[gnome-software/wip/rancell/paid: 369/369] Add support for app purchasing
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/rancell/paid: 369/369] Add support for app purchasing
- Date: Tue, 5 Jul 2016 00:20:43 +0000 (UTC)
commit d56ae03d9aa8510238265c9f408bbd02497e418b
Author: Robert Ancell <robert ancell canonical com>
Date: Tue Feb 23 14:55:05 2016 +1300
Add support for app purchasing
configure.ac | 2 +-
po/POTFILES.in | 3 +
src/Makefile.am | 6 +
src/gnome-software.gresource.xml | 1 +
src/gs-app-row.c | 16 +++
src/gs-app.c | 38 +++++++
src/gs-app.h | 4 +
src/gs-common.c | 5 +
src/gs-page.c | 97 ++++++++++++++++++
src/gs-page.h | 3 +
src/gs-plugin-loader.c | 110 ++++++++++++++++++++
src/gs-plugin-loader.h | 8 ++
src/gs-plugin-vfuncs.h | 22 ++++
src/gs-plugin.h | 1 +
src/gs-price.c | 209 ++++++++++++++++++++++++++++++++++++++
src/gs-price.h | 50 +++++++++
src/gs-purchase-dialog.c | 58 +++++++++++
src/gs-purchase-dialog.h | 39 +++++++
src/gs-purchase-dialog.ui | 80 +++++++++++++++
src/gs-shell-details.c | 25 +++++
src/gs-shell-search.c | 5 +-
src/gs-utils.h | 1 +
src/plugins/gs-plugin-dummy.c | 55 ++++++++++-
23 files changed, 834 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1662005..a3b5277 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,7 +61,7 @@ dnl ---------------------------------------------------------------------------
dnl - Check library dependencies
dnl ---------------------------------------------------------------------------
PKG_CHECK_MODULES(GTK, gtk+-3.0 >= 3.18.2 gio-unix-2.0 gtkspell3-3.0)
-PKG_CHECK_MODULES(APPSTREAM, appstream-glib >= 0.5.15)
+PKG_CHECK_MODULES(APPSTREAM, appstream-glib >= 0.5.17)
PKG_CHECK_MODULES(GDK_PIXBUF, gdk-pixbuf-2.0 >= 2.31.5)
PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0 >= 1.1.1)
PKG_CHECK_MODULES(SQLITE, sqlite3)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 56562af..cbc7064 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -25,6 +25,9 @@ src/gs-main.c
src/gs-page.c
src/gs-plugin-loader.c
src/gs-popular-tile.c
+src/gs-price.c
+src/gs-purchase-dialog.c
+[type: gettext/glade]src/gs-purchase-dialog.ui
src/gs-review-dialog.c
[type: gettext/glade]src/gs-review-dialog.ui
[type: gettext/glade]src/gs-review-histogram.ui
diff --git a/src/Makefile.am b/src/Makefile.am
index 6af354f..2ee4e58 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -59,6 +59,7 @@ UI_FILES = \
gs-info-bar.ui \
gs-menus.ui \
gs-popular-tile.ui \
+ gs-purchase-dialog.ui \
gs-review-dialog.ui \
gs-review-histogram.ui \
gs-review-row.ui \
@@ -109,6 +110,7 @@ gnome_software_cmd_SOURCES = \
gs-app.c \
gs-app-list.c \
gs-auth.c \
+ gs-price.c \
gs-review.c \
gs-cmd.c \
gs-common.c \
@@ -193,8 +195,12 @@ gnome_software_SOURCES = \
gs-plugin.h \
gs-plugin-private.h \
gs-plugin-vfuncs.h \
+ gs-price.c \
+ gs-price.h \
gs-progress-button.c \
gs-progress-button.h \
+ gs-purchase-dialog.c \
+ gs-purchase-dialog.h \
gs-review.c \
gs-review.h \
gs-review-bar.c \
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index d3a9391..c4fd722 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -14,6 +14,7 @@
<file preprocess="xml-stripblanks">gs-history-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-info-bar.ui</file>
<file preprocess="xml-stripblanks">gs-popular-tile.ui</file>
+ <file preprocess="xml-stripblanks">gs-purchase-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-review-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-review-histogram.ui</file>
<file preprocess="xml-stripblanks">gs-review-row.ui</file>
diff --git a/src/gs-app-row.c b/src/gs-app-row.c
index cca5c5d..f6cd4b4 100644
--- a/src/gs-app-row.c
+++ b/src/gs-app-row.c
@@ -30,6 +30,7 @@
#include "gs-progress-button.h"
#include "gs-common.h"
#include "gs-folders.h"
+#include "gs-price.h"
typedef struct
{
@@ -123,6 +124,7 @@ gs_app_row_refresh (GsAppRow *app_row)
gboolean missing_search_result;
gboolean show_nonfree = FALSE;
gboolean use_folders = FALSE;
+ GPtrArray *prices;
if (priv->app == NULL)
return;
@@ -291,6 +293,20 @@ gs_app_row_refresh (GsAppRow *app_row)
* that has been queued for installation */
gtk_label_set_label (GTK_LABEL (priv->label), _("Pending"));
break;
+ case AS_APP_STATE_PURCHASABLE:
+ gtk_widget_set_visible (priv->button, TRUE);
+ prices = gs_app_get_prices (priv->app);
+ if (prices->len > 0) {
+ GsPrice *price = g_ptr_array_index (prices, 0);
+ g_autofree gchar *text = NULL;
+ text = gs_price_to_string (price);
+ gtk_button_set_label (GTK_BUTTON (priv->button), text);
+ } else {
+ /* TRANSLATORS: this is a button next to the search results that
+ * allows the application to be easily purchased */
+ gtk_button_set_label (GTK_BUTTON (priv->button), _("Buy"));
+ }
+ break;
case AS_APP_STATE_AVAILABLE:
case AS_APP_STATE_AVAILABLE_LOCAL:
gtk_widget_set_visible (priv->button, TRUE);
diff --git a/src/gs-app.c b/src/gs-app.c
index 6d027fa..dea1437 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -53,6 +53,7 @@
#include "gs-app-private.h"
#include "gs-plugin.h"
#include "gs-utils.h"
+#include "gs-price.h"
struct _GsApp
{
@@ -90,6 +91,7 @@ struct _GsApp
gchar *management_plugin;
guint match_value;
guint priority;
+ GPtrArray *prices; /* of GsPrice */
gint rating;
GArray *review_ratings;
GPtrArray *reviews; /* of GsReview */
@@ -374,6 +376,13 @@ gs_app_to_string (GsApp *app)
gs_app_kv_lpad (str, "origin", app->origin);
if (app->origin_ui != NULL && app->origin_ui[0] != '\0')
gs_app_kv_lpad (str, "origin-ui", app->origin_ui);
+ for (i = 0; i < app->prices->len; i++) {
+ GsPrice *price = g_ptr_array_index (app->prices, i);
+ g_autofree gchar *key = NULL, *text = NULL;
+ key = g_strdup_printf ("price-%02i", i);
+ text = gs_price_to_string (price);
+ gs_app_kv_lpad (str, key, text);
+ }
if (app->rating != -1)
gs_app_kv_printf (str, "rating", "%i", app->rating);
if (app->review_ratings != NULL) {
@@ -590,6 +599,7 @@ gs_app_set_state_internal (GsApp *app, AsAppState state)
/* unknown has to go into one of the stable states */
if (state == AS_APP_STATE_INSTALLED ||
state == AS_APP_STATE_QUEUED_FOR_INSTALL ||
+ state == AS_APP_STATE_PURCHASABLE ||
state == AS_APP_STATE_AVAILABLE ||
state == AS_APP_STATE_AVAILABLE_LOCAL ||
state == AS_APP_STATE_UPDATABLE ||
@@ -609,6 +619,12 @@ gs_app_set_state_internal (GsApp *app, AsAppState state)
state == AS_APP_STATE_AVAILABLE)
state_change_ok = TRUE;
break;
+ case AS_APP_STATE_PURCHASABLE:
+ /* purchasable has to go into an available state */
+ if (state == AS_APP_STATE_UNKNOWN ||
+ state == AS_APP_STATE_AVAILABLE)
+ state_change_ok = TRUE;
+ break;
case AS_APP_STATE_AVAILABLE:
/* available has to go into an action state */
if (state == AS_APP_STATE_UNKNOWN ||
@@ -1843,6 +1859,26 @@ gs_app_set_management_plugin (GsApp *app, const gchar *management_plugin)
}
/**
+ * gs_app_get_prices:
+ */
+GPtrArray *
+gs_app_get_prices (GsApp *app)
+{
+ g_return_val_if_fail (GS_IS_APP (app), NULL);
+ return app->prices;
+}
+
+/**
+ * gs_app_add_price:
+ */
+void
+gs_app_add_price (GsApp *app, gdouble amount, const gchar *currency)
+{
+ g_return_if_fail (GS_IS_APP (app));
+ g_ptr_array_add (app->prices, gs_price_new (amount, currency));
+}
+
+/**
* gs_app_get_rating:
* @app: a #GsApp
*
@@ -2761,6 +2797,7 @@ gs_app_dispose (GObject *object)
g_clear_pointer (&app->history, g_ptr_array_unref);
g_clear_pointer (&app->related, g_ptr_array_unref);
g_clear_pointer (&app->screenshots, g_ptr_array_unref);
+ g_clear_pointer (&app->prices, g_ptr_array_unref);
g_clear_pointer (&app->reviews, g_ptr_array_unref);
g_clear_pointer (&app->icons, g_ptr_array_unref);
@@ -2919,6 +2956,7 @@ gs_app_init (GsApp *app)
app->related = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->history = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ app->prices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->metadata = g_hash_table_new_full (g_str_hash,
diff --git a/src/gs-app.h b/src/gs-app.h
index a152dda..f491cec 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -199,6 +199,10 @@ const gchar *gs_app_get_metadata_item (GsApp *app,
void gs_app_set_metadata (GsApp *app,
const gchar *key,
const gchar *value);
+GPtrArray *gs_app_get_prices (GsApp *app);
+void gs_app_add_price (GsApp *app,
+ gdouble amount,
+ const gchar *currency);
gint gs_app_get_rating (GsApp *app);
void gs_app_set_rating (GsApp *app,
gint rating);
diff --git a/src/gs-common.c b/src/gs-common.c
index 9e7c026..8c1c31a 100644
--- a/src/gs-common.c
+++ b/src/gs-common.c
@@ -182,6 +182,11 @@ gs_app_notify_failed_modal (GsApp *app,
g_string_append_printf (msg, _("Upgrade to %s failed."), name_version);
break;
}
+ case GS_PLUGIN_LOADER_ACTION_PURCHASE:
+ /* TRANSLATORS: this is when the purchase fails */
+ g_string_append_printf (msg, _("Purchase of %s failed."),
+ gs_app_get_name (app));
+ break;
default:
g_assert_not_reached ();
break;
diff --git a/src/gs-page.c b/src/gs-page.c
index 7cd7de4..0a6804e 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -29,6 +29,7 @@
#include "gs-shell.h"
#include "gs-common.h"
#include "gs-auth-dialog.h"
+#include "gs-price.h"
typedef struct
{
@@ -59,6 +60,33 @@ gs_page_helper_free (GsPageHelper *helper)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
static void
+gs_page_app_purchased_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ GsPage *page = helper->page;
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ gboolean ret;
+ g_autoptr(GError) error = NULL;
+
+ ret = gs_plugin_loader_app_action_finish (plugin_loader,
+ res,
+ &error);
+ if (!ret) {
+ g_warning ("failed to purchase %s: %s",
+ gs_app_get_id (helper->app),
+ error->message);
+ gs_app_notify_failed_modal (helper->app,
+ gs_shell_get_window (priv->shell),
+ GS_PLUGIN_LOADER_ACTION_PURCHASE,
+ error);
+ return;
+ }
+}
+
+static void
gs_page_app_installed_cb (GObject *source,
GAsyncResult *res,
gpointer user_data);
@@ -277,6 +305,75 @@ gs_page_set_header_end_widget (GsPage *page, GtkWidget *widget)
g_set_object (&priv->header_end_widget, widget);
}
+static void
+gs_page_purchase_app_response_cb (GtkDialog *dialog,
+ gint response,
+ GsPageHelper *helper)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+ GPtrArray *prices;
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK) {
+ gs_page_helper_free (helper);
+ return;
+ }
+ g_debug ("purchase %s", gs_app_get_id (helper->app));
+ prices = gs_app_get_prices (helper->app);
+ gs_plugin_loader_app_purchase_async (priv->plugin_loader,
+ helper->app,
+ g_ptr_array_index (prices, 0), // FIXME: User should pick price,
check if no prices
+ priv->cancellable,
+ gs_page_app_purchased_cb,
+ helper);
+}
+
+void
+gs_page_purchase_app (GsPage *page, GsApp *app)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ GsPageHelper *helper;
+ GtkWidget *dialog;
+ GPtrArray *prices;
+ g_autofree gchar *price_text = NULL, *escaped = NULL;
+
+ helper = g_slice_new0 (GsPageHelper);
+ helper->app = g_object_ref (app);
+ helper->page = g_object_ref (page);
+
+ /* ask for confirmation */
+ dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: this is a prompt message, and
+ * '%s' is an application summary, e.g. 'GNOME Clocks' */
+ _("Are you sure you want to purchase %s?"),
+ gs_app_get_name (app));
+ prices = gs_app_get_prices (app);
+ if (prices->len > 0) {
+ GsPrice *price = g_ptr_array_index (prices, 0); // FIXME: Give option of price to choose
+ g_autofree gchar *text = NULL;
+ price_text = gs_price_to_string (price);
+ } else {
+ price_text = g_strdup ("nothing"); // FIXME
+ }
+ escaped = g_markup_escape_text (gs_app_get_name (app), -1);
+ gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+ /* TRANSLATORS: longer dialog text */
+ _("You will be charged %s and %s will be installed."),
+ price_text,
+ escaped);
+
+ /* TRANSLATORS: this is button text to purchase the application */
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("Purchase"), GTK_RESPONSE_OK);
+
+ /* handle this async */
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_purchase_app_response_cb), helper);
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+}
+
void
gs_page_install_app (GsPage *page, GsApp *app)
{
diff --git a/src/gs-page.h b/src/gs-page.h
index 426e3b4..71caf62 100644
--- a/src/gs-page.h
+++ b/src/gs-page.h
@@ -48,12 +48,15 @@ struct _GsPageClass
};
GsPage *gs_page_new (void);
+
GtkWidget *gs_page_get_header_start_widget (GsPage *page);
void gs_page_set_header_start_widget (GsPage *page,
GtkWidget *widget);
GtkWidget *gs_page_get_header_end_widget (GsPage *page);
void gs_page_set_header_end_widget (GsPage *page,
GtkWidget *widget);
+void gs_page_purchase_app (GsPage *page,
+ GsApp *app);
void gs_page_install_app (GsPage *page,
GsApp *app);
void gs_page_remove_app (GsPage *page,
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index f9ff859..8798e2c 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -105,6 +105,9 @@ typedef gboolean (*GsPluginReviewFunc) (GsPlugin *plugin,
GError **error);
typedef gboolean (*GsPluginAuthFunc) (GsPlugin *plugin,
GsAuth *auth,
+typedef gboolean (*GsPluginPurchaseFunc) (GsPlugin *plugin,
+ GsApp *app,
+ GsPrice *price,
GCancellable *cancellable,
GError **error);
typedef gboolean (*GsPluginRefineFunc) (GsPlugin *plugin,
@@ -147,6 +150,7 @@ typedef struct {
GsApp *app;
GsReview *review;
GsAuth *auth;
+ GsPrice *price;
} GsPluginLoaderAsyncState;
static void
@@ -160,6 +164,8 @@ gs_plugin_loader_free_async_state (GsPluginLoaderAsyncState *state)
g_object_unref (state->auth);
if (state->review != NULL)
g_object_unref (state->review);
+ if (state->price != NULL)
+ g_object_unref (state->price);
if (state->file != NULL)
g_object_unref (state->file);
if (state->list != NULL)
@@ -2791,6 +2797,74 @@ gs_plugin_loader_review_action_thread_cb (GTask *task,
g_task_return_boolean (task, TRUE);
}
+/**
+ * gs_plugin_loader_app_purchase_thread_cb:
+ **/
+static void
+gs_plugin_loader_app_purchase_thread_cb (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+ GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+ GsPlugin *plugin;
+ GsPluginPurchaseFunc plugin_func = NULL;
+ gboolean anything_ran = FALSE;
+ gboolean exists;
+ gboolean ret;
+ guint i;
+
+ /* run each plugin */
+ for (i = 0; i < priv->plugins->len; i++) {
+ g_autoptr(AsProfileTask) ptask = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ plugin = g_ptr_array_index (priv->plugins, i);
+ if (!gs_plugin_get_enabled (plugin))
+ continue;
+ if (g_cancellable_set_error_if_cancelled (cancellable, &error))
+ g_task_return_error (task, error);
+
+ exists = g_module_symbol (gs_plugin_get_module (plugin),
+ state->function_name,
+ (gpointer *) &plugin_func);
+ if (!exists)
+ continue;
+ ptask = as_profile_start (priv->profile,
+ "GsPlugin::%s(%s)",
+ gs_plugin_get_name (plugin),
+ state->function_name);
+ gs_plugin_loader_action_start (plugin_loader, plugin, FALSE);
+ ret = plugin_func (plugin, state->app, state->price,
+ cancellable, &error_local);
+ gs_plugin_loader_action_stop (plugin_loader, plugin);
+ if (!ret) {
+ g_warning ("failed to call %s on %s: %s",
+ state->function_name,
+ gs_plugin_get_name (plugin),
+ error_local->message);
+ continue;
+ }
+ anything_ran = TRUE;
+ gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+ }
+
+ /* nothing ran */
+ if (!anything_ran) {
+ g_set_error (&error,
+ GS_PLUGIN_LOADER_ERROR,
+ GS_PLUGIN_LOADER_ERROR_FAILED,
+ "no plugin could handle %s",
+ state->function_name);
+ g_task_return_error (task, error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
static gboolean
load_install_queue (GsPluginLoader *plugin_loader, GError **error)
{
@@ -2991,6 +3065,9 @@ gs_plugin_loader_app_action_async (GsPluginLoader *plugin_loader,
state->app = g_object_ref (app);
switch (action) {
+ case GS_PLUGIN_LOADER_ACTION_PURCHASE:
+ state->function_name = "gs_plugin_app_purchase";
+ break;
case GS_PLUGIN_LOADER_ACTION_INSTALL:
state->function_name = "gs_plugin_app_install";
break;
@@ -3032,6 +3109,39 @@ gs_plugin_loader_app_action_async (GsPluginLoader *plugin_loader,
g_task_run_in_thread (task, gs_plugin_loader_app_action_thread_cb);
}
+/**
+ * gs_plugin_loader_app_purchase_async:
+ **/
+void
+gs_plugin_loader_app_purchase_async (GsPluginLoader *plugin_loader,
+ GsApp *app,
+ GsPrice *price,
+ 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 (GS_IS_APP (app));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ /* save state */
+ state = g_slice_new0 (GsPluginLoaderAsyncState);
+ state->app = g_object_ref (app);
+ state->price = g_object_ref (price);
+ state->function_name = "gs_plugin_app_purchase";
+
+ /* 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_run_in_thread (task, gs_plugin_loader_app_purchase_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_review_action_async:
+ **/
void
gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
GsApp *app,
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 1214d53..7f9716d 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -47,6 +47,7 @@ struct _GsPluginLoaderClass
};
typedef enum {
+ GS_PLUGIN_LOADER_ACTION_PURCHASE,
GS_PLUGIN_LOADER_ACTION_INSTALL,
GS_PLUGIN_LOADER_ACTION_REMOVE,
GS_PLUGIN_LOADER_ACTION_UPDATE,
@@ -216,6 +217,13 @@ gboolean gs_plugin_loader_app_action_finish (GsPluginLoader *plugin_loader,
GAsyncResult *res,
GError **error);
gboolean gs_plugin_loader_review_action_finish (GsPluginLoader *plugin_loader,
+void gs_plugin_loader_app_purchase_async (GsPluginLoader *plugin_loader,
+ GsApp *app,
+ GsPrice *price,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gs_plugin_loader_app_purchase_finish (GsPluginLoader *plugin_loader,
GAsyncResult *res,
GError **error);
void gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
diff --git a/src/gs-plugin-vfuncs.h b/src/gs-plugin-vfuncs.h
index 0473515..d71dc52 100644
--- a/src/gs-plugin-vfuncs.h
+++ b/src/gs-plugin-vfuncs.h
@@ -497,6 +497,27 @@ gboolean gs_plugin_update_cancel (GsPlugin *plugin,
GError **error);
/**
+ * gs_plugin_app_purchase:
+ * @plugin: a #GsPlugin
+ * @app: a #GsApp
+ * @price: a #GsPrice
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Purchase the application.
+ *
+ * NOTE: Once the action is complete, the plugin must set the new state of @app
+ * to %AS_APP_STATE_AVAILABLE.
+ *
+ * Returns: %TRUE for success or if not relevant
+ **/
+gboolean gs_plugin_app_purchase (GsPlugin *plugin,
+ GsApp *app,
+ GsPrice *price,
+ GCancellable *cancellable,
+ GError **error);
+
+/**
* gs_plugin_app_install:
* @plugin: a #GsPlugin
* @app: a #GsApp
@@ -913,6 +934,7 @@ gboolean gs_plugin_auth_register (GsPlugin *plugin,
GCancellable *cancellable,
GError **error);
+
G_END_DECLS
#endif /* __GS_PLUGIN_VFUNCS_H */
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index aa2bb86..23a2eae 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -32,6 +32,7 @@
#include "gs-app-list.h"
#include "gs-auth.h"
#include "gs-category.h"
+#include "gs-price.h"
G_BEGIN_DECLS
diff --git a/src/gs-price.c b/src/gs-price.c
new file mode 100644
index 0000000..69ac267
--- /dev/null
+++ b/src/gs-price.c
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd.
+ *
+ * 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 <glib/gi18n.h>
+
+#include "gs-price.h"
+
+struct _GsPrice
+{
+ GObject parent_instance;
+
+ gdouble amount;
+ gchar *currency;
+};
+
+enum {
+ PROP_0,
+ PROP_AMOUNT,
+ PROP_CURRENCY,
+ PROP_LAST
+};
+
+G_DEFINE_TYPE (GsPrice, gs_price, G_TYPE_OBJECT)
+
+/**
+ * gs_price_get_amount:
+ */
+gdouble
+gs_price_get_amount (GsPrice *price)
+{
+ g_return_val_if_fail (GS_IS_PRICE (price), 0);
+ return price->amount;
+}
+
+/**
+ * gs_price_set_amount:
+ */
+void
+gs_price_set_amount (GsPrice *price, gdouble amount)
+{
+ g_return_if_fail (GS_IS_PRICE (price));
+ price->amount = amount;
+}
+
+/**
+ * gs_price_get_currency:
+ */
+const gchar *
+gs_price_get_currency (GsPrice *price)
+{
+ g_return_val_if_fail (GS_IS_PRICE (price), NULL);
+ return price->currency;
+}
+
+/**
+ * gs_price_set_currency:
+ */
+void
+gs_price_set_currency (GsPrice *price, const gchar *currency)
+{
+ g_return_if_fail (GS_IS_PRICE (price));
+ g_free (price->currency);
+ price->currency = g_strdup (currency);
+}
+
+/**
+ * gs_price_to_string:
+ */
+gchar *
+gs_price_to_string (GsPrice *price)
+{
+ g_return_val_if_fail (GS_IS_PRICE (price), NULL);
+
+ if (g_strcmp0 (price->currency, "AUD") == 0) {
+ return g_strdup_printf (_("A$%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "CAD") == 0) {
+ return g_strdup_printf (_("C$%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "CNY") == 0) {
+ return g_strdup_printf (_("CN¥%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "EUR") == 0) {
+ return g_strdup_printf (_("€%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "GBP") == 0) {
+ return g_strdup_printf (_("£%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "JPY") == 0) {
+ return g_strdup_printf (_("¥%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "NZD") == 0) {
+ return g_strdup_printf (_("NZ$%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "RUB") == 0) {
+ return g_strdup_printf (_("₽%.2f"), price->amount);
+ } else if (g_strcmp0 (price->currency, "USD") == 0) {
+ return g_strdup_printf (_("US$%.2f"), price->amount);
+ } else {
+ return g_strdup_printf (_("%s %f"), price->currency, price->amount);
+ }
+}
+
+static void
+gs_price_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GsPrice *price = GS_PRICE (object);
+
+ switch (prop_id) {
+ case PROP_AMOUNT:
+ g_value_set_double (value, price->amount);
+ break;
+ case PROP_CURRENCY:
+ g_value_set_string (value, price->currency);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_price_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GsPrice *price = GS_PRICE (object);
+
+ switch (prop_id) {
+ case PROP_AMOUNT:
+ gs_price_set_amount (price, g_value_get_double (value));
+ break;
+ case PROP_CURRENCY:
+ gs_price_set_currency (price, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_price_finalize (GObject *object)
+{
+ GsPrice *price = GS_PRICE (object);
+
+ g_free (price->currency);
+
+ G_OBJECT_CLASS (gs_price_parent_class)->finalize (object);
+}
+
+static void
+gs_price_class_init (GsPriceClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gs_price_finalize;
+ object_class->get_property = gs_price_get_property;
+ object_class->set_property = gs_price_set_property;
+
+ /**
+ * GsApp:amount:
+ */
+ pspec = g_param_spec_double ("amount", NULL, NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_AMOUNT, pspec);
+
+ /**
+ * GsApp:currency:
+ */
+ pspec = g_param_spec_string ("currency", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_CURRENCY, pspec);
+}
+
+static void
+gs_price_init (GsPrice *price)
+{
+}
+
+/**
+ * gs_price_new:
+ *
+ * Return value: a new #GsPrice object.
+ **/
+GsPrice *
+gs_price_new (gdouble amount, const gchar *currency)
+{
+ GsPrice *price;
+ price = g_object_new (GS_TYPE_PRICE, "amount", amount, "currency", currency, NULL);
+ return GS_PRICE (price);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-price.h b/src/gs-price.h
new file mode 100644
index 0000000..aab8677
--- /dev/null
+++ b/src/gs-price.h
@@ -0,0 +1,50 @@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd.
+ *
+ * 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_PRICE_H
+#define __GS_PRICE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PRICE (gs_price_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPrice, gs_price, GS, PRICE, GObject)
+
+GsPrice *gs_price_new (gdouble amount,
+ const gchar *currency);
+
+gdouble gs_price_get_amount (GsPrice *price);
+void gs_price_set_amount (GsPrice *price,
+ gdouble amount);
+
+const gchar *gs_price_get_currency (GsPrice *price);
+void gs_price_set_currency (GsPrice *price,
+ const gchar *currency);
+
+gchar *gs_price_to_string (GsPrice *price);
+
+G_END_DECLS
+
+#endif /* __GS_PRICE_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-purchase-dialog.c b/src/gs-purchase-dialog.c
new file mode 100644
index 0000000..2e332a1
--- /dev/null
+++ b/src/gs-purchase-dialog.c
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd.
+ *
+ * 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 <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-purchase-dialog.h"
+
+struct _GsPurchaseDialog
+{
+ GtkDialog parent_instance;
+};
+
+G_DEFINE_TYPE (GsPurchaseDialog, gs_purchase_dialog, GTK_TYPE_DIALOG)
+
+static void
+gs_purchase_dialog_init (GsPurchaseDialog *dialog)
+{
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+}
+
+static void
+gs_purchase_dialog_class_init (GsPurchaseDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Software/gs-purchase-dialog.ui");
+}
+
+GtkWidget *
+gs_purchase_dialog_new (void)
+{
+ return GTK_WIDGET (g_object_new (GS_TYPE_PURCHASE_DIALOG,
+ "use-header-bar", TRUE,
+ NULL));
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-purchase-dialog.h b/src/gs-purchase-dialog.h
new file mode 100644
index 0000000..e91ff21
--- /dev/null
+++ b/src/gs-purchase-dialog.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd.
+ *
+ * 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_PURCHASE_DIALOG_H
+#define GS_PURCHASE_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PURCHASE_DIALOG (gs_purchase_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPurchaseDialog, gs_purchase_dialog, GS, PURCHASE_DIALOG, GtkDialog)
+
+GtkWidget *gs_purchase_dialog_new (void);
+
+G_END_DECLS
+
+#endif /* GS_PURCHASE_DIALOG_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-purchase-dialog.ui b/src/gs-purchase-dialog.ui
new file mode 100644
index 0000000..453275d
--- /dev/null
+++ b/src/gs-purchase-dialog.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <template class="GsReviewDialog" parent="GtkDialog">
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="ok">purchase_button</action-widget>
+ </action-widgets>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes" comments="Translators: Title of the dialog box where users
purchase apps.">Purchase</property>
+ <property name="modal">True</property>
+ <property name="default_width">600</property>
+ <property name="default_height">300</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use_header_bar">1</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="show_close_button">False</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="purchase_button">
+ <property name="label" translatable="yes" comments="Translators: A button to purchase an
app.">_Purchase</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox">
+ <property name="can_focus">False</property>
+ <property name="margin_start">40</property>
+ <property name="margin_end">40</property>
+ <property name="margin_top">25</property>
+ <property name="margin_bottom">25</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">9</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup" id="sizegroup_folder_buttons">
+ <property name="ignore-hidden">False</property>
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="cancel_button"/>
+ <widget name="post_button"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 26ee6d0..725dad5 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -38,6 +38,7 @@
#include "gs-review-histogram.h"
#include "gs-review-dialog.h"
#include "gs-review-row.h"
+#include "gs-price.h"
/* the number of reviews to show before clicking the 'More Reviews' button */
#define SHOW_NR_REVIEWS_INITIAL 4
@@ -220,6 +221,7 @@ gs_shell_details_switch_to (GsPage *page, gboolean scroll_up)
GtkWidget *widget;
GtkStyleContext *sc;
GtkAdjustment *adj;
+ GPtrArray *prices;
if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_DETAILS) {
g_warning ("Called switch_to(details) when in mode %s",
@@ -248,6 +250,22 @@ gs_shell_details_switch_to (GsPage *page, gboolean scroll_up)
/* install button */
switch (state) {
+ case AS_APP_STATE_PURCHASABLE:
+ gtk_widget_set_visible (self->button_install, TRUE);
+ gtk_widget_set_sensitive (self->button_install, TRUE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (self->button_install),
"suggested-action");
+ prices = gs_app_get_prices (self->app);
+ if (prices->len > 0) {
+ GsPrice *price = g_ptr_array_index (prices, 0);
+ g_autofree gchar *text = NULL;
+ text = gs_price_to_string (price);
+ gtk_button_set_label (GTK_BUTTON (self->button_install), text);
+ } else {
+ /* TRANSLATORS: button text in the header when an application
+ * can be purchased */
+ gtk_button_set_label (GTK_BUTTON (self->button_install), _("_Buy"));
+ }
+ break;
case AS_APP_STATE_AVAILABLE:
case AS_APP_STATE_AVAILABLE_LOCAL:
gtk_widget_set_visible (self->button_install, TRUE);
@@ -361,6 +379,7 @@ gs_shell_details_switch_to (GsPage *page, gboolean scroll_up)
gtk_style_context_remove_class (gtk_widget_get_style_context (self->button_remove),
"destructive-action");
gtk_button_set_label (GTK_BUTTON (self->button_remove), _("_Cancel"));
break;
+ case AS_APP_STATE_PURCHASABLE:
case AS_APP_STATE_AVAILABLE_LOCAL:
case AS_APP_STATE_AVAILABLE:
case AS_APP_STATE_INSTALLING:
@@ -393,6 +412,7 @@ gs_shell_details_switch_to (GsPage *page, gboolean scroll_up)
switch (state) {
case AS_APP_STATE_UNKNOWN:
case AS_APP_STATE_INSTALLED:
+ case AS_APP_STATE_PURCHASABLE:
case AS_APP_STATE_AVAILABLE:
case AS_APP_STATE_QUEUED_FOR_INSTALL:
case AS_APP_STATE_UPDATABLE:
@@ -1607,6 +1627,11 @@ gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *self)
GList *l;
g_autoptr(GList) addons = NULL;
+ if (gs_app_get_state (self->app) == AS_APP_STATE_PURCHASABLE) {
+ gs_page_purchase_app (GS_PAGE (self), self->app);
+ return;
+ }
+
/* Mark ticked addons to be installed together with the app */
addons = gtk_container_get_children (GTK_CONTAINER (self->list_box_addons));
for (l = addons; l; l = l->next) {
diff --git a/src/gs-shell-search.c b/src/gs-shell-search.c
index 21b2e67..763f55b 100644
--- a/src/gs-shell-search.c
+++ b/src/gs-shell-search.c
@@ -69,8 +69,11 @@ gs_shell_search_app_row_clicked_cb (GsAppRow *app_row,
GsShellSearch *self)
{
GsApp *app;
+
app = gs_app_row_get_app (app_row);
- if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE)
+ if (gs_app_get_state (app) == AS_APP_STATE_PURCHASABLE)
+ gs_page_purchase_app (GS_PAGE (self), app);
+ else if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE)
gs_page_install_app (GS_PAGE (self), app);
else if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
gs_page_remove_app (GS_PAGE (self), app);
diff --git a/src/gs-utils.h b/src/gs-utils.h
index 884ab91..6cb7a8b 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -65,6 +65,7 @@ gboolean gs_utils_strv_fnmatch (gchar **strv,
GDesktopAppInfo *gs_utils_get_desktop_app_info (const gchar *id);
gboolean gs_utils_rmtree (const gchar *directory,
GError **error);
+
G_END_DECLS
#endif /* __GS_UTILS_H */
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index 67df02b..6868002 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -145,13 +145,31 @@ gs_plugin_add_search (GsPlugin *plugin,
GError **error)
{
GsPluginData *priv = gs_plugin_get_data (plugin);
- g_autoptr(GsApp) app = NULL;
+ g_autoptr(GsApp) app = NULL, app2 = NULL;
g_autoptr(AsIcon) ic = NULL;
/* we're very specific */
if (g_strcmp0 (values[0], "chiron") != 0)
return TRUE;
+ /* use a generic stock icon */
+ ic = as_icon_new ();
+ as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
+ as_icon_set_name (ic, "drive-harddisk");
+
+ /* add a live updatable normal application */
+ app2 = gs_app_new ("chiron-paid.desktop");
+ gs_app_set_name (app2, GS_APP_QUALITY_NORMAL, "Chiron (paid)");
+ gs_app_set_summary (app2, GS_APP_QUALITY_NORMAL, "A teaching application");
+ gs_app_add_price (app2, 100, "USD");
+ gs_app_set_icon (app2, ic);
+ gs_app_set_size_installed (app2, 42 * 1024 * 1024);
+ gs_app_set_size_download (app2, 50 * 1024 * 1024);
+ gs_app_set_kind (app2, AS_APP_KIND_DESKTOP);
+ gs_app_set_state (app2, AS_APP_STATE_PURCHASABLE);
+ gs_app_set_management_plugin (app2, gs_plugin_get_name (plugin));
+ gs_app_list_add (list, app2);
+
/* does the app already exist? */
app = gs_plugin_cache_lookup (plugin, "chiron");
if (app != NULL) {
@@ -491,7 +509,9 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
- g_autoptr(GsApp) app = gs_app_new ("chiron.desktop");
+ g_autoptr(GsApp) app = NULL, app2 = NULL;
+
+ app = gs_app_new ("chiron.desktop");
gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Chiron");
gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "View and use virtual machines");
gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, "http://www.box.org");
@@ -501,6 +521,19 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
gs_app_list_add (list, app);
+
+ app2 = gs_app_new ("chiron-paid.desktop");
+ gs_app_set_name (app2, GS_APP_QUALITY_NORMAL, "Expensive App");
+ gs_app_set_summary (app2, GS_APP_QUALITY_NORMAL, "An app that costs you money");
+ gs_app_add_price (app2, 100, "USD");
+ gs_app_set_url (app2, AS_URL_KIND_HOMEPAGE, "http://www.example.com");
+ gs_app_set_kind (app2, AS_APP_KIND_DESKTOP);
+ gs_app_set_state (app2, AS_APP_STATE_PURCHASABLE);
+ gs_app_set_pixbuf (app2, gdk_pixbuf_new_from_file
("/usr/share/icons/hicolor/48x48/apps/chiron.desktop.png", NULL));
+ gs_app_set_kind (app2, AS_APP_KIND_DESKTOP);
+ gs_app_set_management_plugin (app2, gs_plugin_get_name (plugin));
+ gs_app_list_add (list, app2);
+
return TRUE;
}
@@ -618,6 +651,24 @@ gs_plugin_update_cancel (GsPlugin *plugin, GsApp *app,
return TRUE;
}
+/**
+ * gs_plugin_app_purchase:
+ */
+gboolean
+gs_plugin_app_purchase (GsPlugin *plugin,
+ GsApp *app,
+ GsPrice *price,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_debug ("Purchasing app");
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+ return TRUE;
+}
+
+/**
+ * gs_plugin_review_submit:
+ */
gboolean
gs_plugin_review_submit (GsPlugin *plugin,
GsApp *app,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]