diff --git a/lib/gs-app.c b/lib/gs-app.c index fdec16795135d85c0b31a60ea7c412bfb7fe7a94..d3cd675464dcc6d9282ddac0da77c40ae1b595fb 100644 --- a/lib/gs-app.c +++ b/lib/gs-app.c @@ -127,6 +127,7 @@ typedef struct GsPluginAction pending_action; GsAppPermissions permissions; gboolean is_update_downloaded; + GPtrArray *version_history; /* (element-type AsRelease) */ } GsAppPrivate; enum { @@ -4584,6 +4585,7 @@ gs_app_dispose (GObject *object) g_clear_pointer (&priv->reviews, g_ptr_array_unref); g_clear_pointer (&priv->provided, g_ptr_array_unref); g_clear_pointer (&priv->icons, g_ptr_array_unref); + g_clear_pointer (&priv->version_history, g_ptr_array_unref); G_OBJECT_CLASS (gs_app_parent_class)->dispose (object); } @@ -5121,3 +5123,43 @@ gs_app_set_update_permissions (GsApp *app, GsAppPermissions update_permissions) g_return_if_fail (GS_IS_APP (app)); priv->update_permissions = update_permissions; } + +/** + * gs_app_get_version_history: + * @app: a #GsApp + * + * Gets the list of past releases for an application (including the latest + * one). + * + * Returns: (element-type AsRelease) (transfer none): a list + * + * Since: 40 + **/ +GPtrArray * +gs_app_get_version_history (GsApp *app) +{ + GsAppPrivate *priv = gs_app_get_instance_private (app); + g_return_val_if_fail (GS_IS_APP (app), NULL); + return priv->version_history; +} + +/** + * gs_app_set_version_history: + * @app: a #GsApp + * @version_history: (element-type AsRelease): a set of entries representing + * the version history + * + * Set the list of past releases for an application (including the latest one). + * + * Since: 40 + **/ +void +gs_app_set_version_history (GsApp *app, GPtrArray *version_history) +{ + GsAppPrivate *priv = gs_app_get_instance_private (app); + g_autoptr(GMutexLocker) locker = NULL; + g_return_if_fail (GS_IS_APP (app)); + g_return_if_fail (version_history != NULL); + locker = g_mutex_locker_new (&priv->mutex); + _g_set_ptr_array (&priv->version_history, version_history); +} diff --git a/lib/gs-app.h b/lib/gs-app.h index 6b7dd5b46f74bf2da21cae5a42956ff9b677dba6..f215151e5e115aac7f7ec78482788a94efcdb031 100644 --- a/lib/gs-app.h +++ b/lib/gs-app.h @@ -466,5 +466,8 @@ void gs_app_set_permissions (GsApp *app, GsAppPermissions gs_app_get_update_permissions (GsApp *app); void gs_app_set_update_permissions (GsApp *app, GsAppPermissions update_permissions); +GPtrArray *gs_app_get_version_history (GsApp *app); +void gs_app_set_version_history (GsApp *app, + GPtrArray *version_history); G_END_DECLS diff --git a/plugins/core/gs-appstream.c b/plugins/core/gs-appstream.c index b0fe2e42fe6bbbb3157114d5b691437b706f679a..6fd38cc4c24abb69c437f12ab28cd6bd1d4ca6a7 100644 --- a/plugins/core/gs-appstream.c +++ b/plugins/core/gs-appstream.c @@ -587,6 +587,63 @@ gs_appstream_refine_app_updates (GsPlugin *plugin, return TRUE; } +static gboolean +gs_appstream_refine_add_version_history (GsApp *app, XbNode *component, GError **error) +{ + g_autoptr(GError) error_local = NULL; + g_autoptr(GPtrArray) version_history = NULL; /* (element-type AsRelease) */ + g_autoptr(GPtrArray) releases = NULL; /* (element-type XbNode) */ + + /* get all components */ + releases = xb_node_query (component, "releases/*", 0, &error_local); + if (releases == NULL) { + if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + return TRUE; + if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) + return TRUE; + g_propagate_error (error, g_steal_pointer (&error_local)); + return FALSE; + } + + version_history = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + for (guint i = 0; i < releases->len; i++) { + XbNode *release_node = g_ptr_array_index (releases, i); + const gchar *version = xb_node_get_attr (release_node, "version"); + g_autoptr(XbNode) description_node = NULL; + g_autofree gchar *description = NULL; + guint64 timestamp; + g_autoptr(AsRelease) release = NULL; + g_autofree char *timestamp_xpath = NULL; + + /* ignore releases with no version */ + if (version == NULL) + continue; + + timestamp_xpath = g_strdup_printf ("releases/release[%u]", i+1); + timestamp = xb_node_query_attr_as_uint (component, timestamp_xpath, "timestamp", NULL); + + /* include updates with or without a description */ + description_node = xb_node_query_first (release_node, "description", NULL); + if (description_node != NULL) + description = gs_appstream_format_description (description_node, NULL); + + release = as_release_new (); + as_release_set_version (release, version); + if (timestamp != G_MAXUINT64) + as_release_set_timestamp (release, timestamp); + if (description != NULL) + as_release_set_description (release, description, NULL); + + g_ptr_array_add (version_history, g_steal_pointer (&release)); + } + + if (version_history->len > 0) + gs_app_set_version_history (app, version_history); + + /* success */ + return TRUE; +} + /** * _gs_utils_locale_has_translations: * @locale: A locale, e.g. `en_GB` or `uz_UZ.utf8@cyrillic` @@ -923,6 +980,10 @@ gs_appstream_refine_app (GsPlugin *plugin, if (timestamp != G_MAXUINT64) gs_app_set_release_date (app, timestamp); + /* set the version history */ + if (!gs_appstream_refine_add_version_history (app, component, error)) + return FALSE; + /* copy all the metadata */ if (!gs_appstream_copy_metadata (app, component, error)) return FALSE; diff --git a/po/POTFILES.in b/po/POTFILES.in index c119b8628510f3357b4952675f52b2da0b060a17..9f92fa4f52dbfc427ae81cfb920fb0deaffc97cd 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,6 +6,9 @@ src/gnome-software.ui lib/gs-app.c src/gs-app-addon-row.c src/gs-app-addon-row.ui +src/gs-app-version-history-dialog.ui +src/gs-app-version-history-row.c +src/gs-app-version-history-row.ui src/gs-application.c src/gs-app-row.c src/gs-app-row.ui diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml index 459ecf82156e7122305a40478b4c28e5947be6ea..d0f7ea95c9cc9e88faa2521e30660cecc59b4e5b 100644 --- a/src/gnome-software.gresource.xml +++ b/src/gnome-software.gresource.xml @@ -3,6 +3,8 @@ gnome-software.ui gs-app-addon-row.ui + gs-app-version-history-dialog.ui + gs-app-version-history-row.ui gs-app-row.ui gs-basic-auth-dialog.ui gs-category-page.ui diff --git a/src/gs-app-version-history-dialog.c b/src/gs-app-version-history-dialog.c new file mode 100644 index 0000000000000000000000000000000000000000..439ad29467e73a44b7e092708249580f3ed37630 --- /dev/null +++ b/src/gs-app-version-history-dialog.c @@ -0,0 +1,109 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2021 Matthew Leeds + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include "gs-app-version-history-dialog.h" + +#include "gnome-software-private.h" +#include "gs-common.h" +#include "gs-app-version-history-row.h" +#include + +struct _GsAppVersionHistoryDialog +{ + GtkDialog parent_instance; + GsApp *app; + GtkWidget *listbox; +}; + +G_DEFINE_TYPE (GsAppVersionHistoryDialog, gs_app_version_history_dialog, GTK_TYPE_DIALOG) + +static void +populate_version_history (GsAppVersionHistoryDialog *dialog, + GsApp *app) +{ + GPtrArray *version_history; + + /* remove previous */ + gs_container_remove_all (GTK_CONTAINER (dialog->listbox)); + + version_history = gs_app_get_version_history (app); + if (version_history == NULL) { + GtkWidget *row; + row = gs_app_version_history_row_new (); + gs_app_version_history_row_set_info (GS_APP_VERSION_HISTORY_ROW (row), + gs_app_get_version (app), + gs_app_get_release_date (app), NULL); + gtk_list_box_insert (GTK_LIST_BOX (dialog->listbox), row, -1); + gtk_widget_show (row); + return; + } + + /* add each */ + for (guint i = 0; i < version_history->len; i++) { + GtkWidget *row; + AsRelease *version = g_ptr_array_index (version_history, i); + + row = gs_app_version_history_row_new (); + gs_app_version_history_row_set_info (GS_APP_VERSION_HISTORY_ROW (row), + as_release_get_version (version), + as_release_get_timestamp (version), + as_release_get_description (version)); + + gtk_list_box_insert (GTK_LIST_BOX (dialog->listbox), row, -1); + gtk_widget_show (row); + } +} + +static void +list_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header = NULL; + if (before != NULL) + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (row, header); +} + +static void +gs_app_version_history_dialog_init (GsAppVersionHistoryDialog *dialog) +{ + gtk_widget_init_template (GTK_WIDGET (dialog)); + + gtk_list_box_set_header_func (GTK_LIST_BOX (dialog->listbox), + list_header_func, + dialog, + NULL); +} + +static void +gs_app_version_history_dialog_class_init (GsAppVersionHistoryDialogClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-app-version-history-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsAppVersionHistoryDialog, listbox); +} + +GtkWidget * +gs_app_version_history_dialog_new (GtkWindow *parent, GsApp *app) +{ + GsAppVersionHistoryDialog *dialog; + + dialog = g_object_new (GS_TYPE_APP_VERSION_HISTORY_DIALOG, + "use-header-bar", TRUE, + "transient-for", parent, + "modal", TRUE, + NULL); + populate_version_history (dialog, app); + + return GTK_WIDGET (dialog); +} diff --git a/src/gs-app-version-history-dialog.h b/src/gs-app-version-history-dialog.h new file mode 100644 index 0000000000000000000000000000000000000000..6abed18e2eb735ece669031c62ed0c7311c81b56 --- /dev/null +++ b/src/gs-app-version-history-dialog.h @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2021 Matthew Leeds + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#pragma once + +#include + +#include "gnome-software-private.h" + +G_BEGIN_DECLS + +#define GS_TYPE_APP_VERSION_HISTORY_DIALOG (gs_app_version_history_dialog_get_type ()) + +G_DECLARE_FINAL_TYPE (GsAppVersionHistoryDialog, gs_app_version_history_dialog, GS, APP_VERSION_HISTORY_DIALOG, GtkDialog) + +GtkWidget *gs_app_version_history_dialog_new (GtkWindow *parent, + GsApp *app); + +G_END_DECLS diff --git a/src/gs-app-version-history-dialog.ui b/src/gs-app-version-history-dialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..b8527a0415b1b132adfc79c64e6d5c2dbb5d41d2 --- /dev/null +++ b/src/gs-app-version-history-dialog.ui @@ -0,0 +1,51 @@ + + + + + diff --git a/src/gs-app-version-history-row.c b/src/gs-app-version-history-row.c new file mode 100644 index 0000000000000000000000000000000000000000..e01e6987e6f60751b91bfde3b6c854aa736fa93e --- /dev/null +++ b/src/gs-app-version-history-row.c @@ -0,0 +1,104 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2021 Matthew Leeds + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include + +#include "gs-app-version-history-row.h" + +#include "gs-common.h" + +struct _GsAppVersionHistoryRow +{ + GtkListBoxRow parent_instance; + + GtkWidget *version_number_label; + GtkWidget *version_date_label; + GtkWidget *version_description_label; +}; + +G_DEFINE_TYPE (GsAppVersionHistoryRow, gs_app_version_history_row, GTK_TYPE_LIST_BOX_ROW) + +static void +gs_app_version_history_row_class_init (GsAppVersionHistoryRowClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-app-version-history-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsAppVersionHistoryRow, version_number_label); + gtk_widget_class_bind_template_child (widget_class, GsAppVersionHistoryRow, version_date_label); + gtk_widget_class_bind_template_child (widget_class, GsAppVersionHistoryRow, version_description_label); +} + +static void +gs_app_version_history_row_init (GsAppVersionHistoryRow *row) +{ + gtk_widget_set_has_window (GTK_WIDGET (row), FALSE); + gtk_widget_init_template (GTK_WIDGET (row)); +} + +void +gs_app_version_history_row_set_info (GsAppVersionHistoryRow *row, + const char *version_number, + guint64 version_date, + const char *version_description) +{ + g_autofree char *version_date_string = NULL; + g_autofree char *version_date_string_tooltip = NULL; + + if (version_number == NULL || *version_number == '\0') + return; + + if (version_description != NULL && *version_description != '\0') { + g_autofree char *version_tmp = NULL; + version_tmp = g_strdup_printf (_("New in Version %s"), version_number); + gtk_label_set_label (GTK_LABEL (row->version_number_label), version_tmp); + gtk_label_set_label (GTK_LABEL (row->version_description_label), version_description); + } else { + g_autofree char *version_tmp = NULL; + const gchar *version_description_fallback; + version_tmp = g_strdup_printf (_("Version %s"), version_number); + gtk_label_set_label (GTK_LABEL (row->version_number_label), version_tmp); + version_description_fallback = _("No details for this release"); + gtk_label_set_label (GTK_LABEL (row->version_description_label), version_description_fallback); + gtk_style_context_add_class (gtk_widget_get_style_context (row->version_description_label), "dim-label"); + } + + if (version_date != 0) { + g_autoptr(GDateTime) date_time = NULL; + const gchar *format_string; + + /* this is the date in the form of "x weeks ago" or "y months ago" */ + version_date_string = gs_utils_time_to_string ((gint64) version_date); + + /* TRANSLATORS: This is the date string with: day number, month name, year. + i.e. "25 May 2012" */ + format_string = _("%e %B %Y"); + date_time = g_date_time_new_from_unix_local (version_date); + version_date_string_tooltip = g_date_time_format (date_time, format_string); + } + + if (version_date_string == NULL) + gtk_widget_set_visible (row->version_date_label, FALSE); + else + gtk_label_set_label (GTK_LABEL (row->version_date_label), version_date_string); + + if (version_date_string_tooltip != NULL) + gtk_widget_set_tooltip_text (row->version_date_label, version_date_string_tooltip); +} + +GtkWidget * +gs_app_version_history_row_new (void) +{ + GsAppVersionHistoryRow *row; + + row = g_object_new (GS_TYPE_APP_VERSION_HISTORY_ROW, NULL); + return GTK_WIDGET (row); +} diff --git a/src/gs-app-version-history-row.h b/src/gs-app-version-history-row.h new file mode 100644 index 0000000000000000000000000000000000000000..0e91ac6e2c8e9295f5bbf3707e1e9d526472ac97 --- /dev/null +++ b/src/gs-app-version-history-row.h @@ -0,0 +1,27 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2021 Matthew Leeds + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#pragma once + +#include + +#include "gnome-software-private.h" + +G_BEGIN_DECLS + +#define GS_TYPE_APP_VERSION_HISTORY_ROW (gs_app_version_history_row_get_type ()) + +G_DECLARE_FINAL_TYPE (GsAppVersionHistoryRow, gs_app_version_history_row, GS, APP_VERSION_HISTORY_ROW, GtkListBoxRow) + +GtkWidget *gs_app_version_history_row_new (void); +void gs_app_version_history_row_set_info (GsAppVersionHistoryRow *row, + const char *version_number, + guint64 version_date, + const char *version_description); + +G_END_DECLS diff --git a/src/gs-app-version-history-row.ui b/src/gs-app-version-history-row.ui new file mode 100644 index 0000000000000000000000000000000000000000..97b016dbb913212527ff9d7a7e6fdf708ce1a1b6 --- /dev/null +++ b/src/gs-app-version-history-row.ui @@ -0,0 +1,63 @@ + + + + + diff --git a/src/gs-common.c b/src/gs-common.c index 404f9ae93b4c436d2b810111fb0ff198e8c7a8a3..dd1ba3f51bce67056e0ae68fbb62760db4ba2425 100644 --- a/src/gs-common.c +++ b/src/gs-common.c @@ -14,6 +14,12 @@ #include "gs-common.h" +#ifdef HAVE_GSETTINGS_DESKTOP_SCHEMAS +#include +#endif + +#include + #define SPINNER_DELAY 500 static gboolean @@ -654,3 +660,64 @@ gs_utils_reboot_notify (GsAppList *list) g_notification_set_priority (n, G_NOTIFICATION_PRIORITY_URGENT); g_application_send_notification (g_application_get_default (), "restart-required", n); } + +/** + * gs_utils_time_to_string: + * @unix_time_seconds: Time since the epoch in seconds + * + * Converts a time to a string such as "5 minutes ago" or "2 weeks ago" + * + * Returns: (transfer full): the time string, or %NULL if @unix_time_seconds is + * not valid + */ +gchar * +gs_utils_time_to_string (gint64 unix_time_seconds) +{ + gint minutes_ago, hours_ago, days_ago; + gint weeks_ago, months_ago, years_ago; + g_autoptr(GDateTime) date_time = NULL; + g_autoptr(GDateTime) now = NULL; + GTimeSpan timespan; + + if (unix_time_seconds <= 0) + return NULL; + + date_time = g_date_time_new_from_unix_local (unix_time_seconds); + now = g_date_time_new_now_local (); + timespan = g_date_time_difference (now, date_time); + + minutes_ago = (gint) (timespan / G_TIME_SPAN_MINUTE); + hours_ago = (gint) (timespan / G_TIME_SPAN_HOUR); + days_ago = (gint) (timespan / G_TIME_SPAN_DAY); + weeks_ago = days_ago / 7; + months_ago = days_ago / 30; + years_ago = weeks_ago / 52; + + if (minutes_ago < 5) { + /* TRANSLATORS: something happened less than 5 minutes ago */ + return g_strdup (_("Just now")); + } else if (hours_ago < 1) + return g_strdup_printf (ngettext ("%d minute ago", + "%d minutes ago", minutes_ago), + minutes_ago); + else if (days_ago < 1) + return g_strdup_printf (ngettext ("%d hour ago", + "%d hours ago", hours_ago), + hours_ago); + else if (days_ago < 15) + return g_strdup_printf (ngettext ("%d day ago", + "%d days ago", days_ago), + days_ago); + else if (weeks_ago < 8) + return g_strdup_printf (ngettext ("%d week ago", + "%d weeks ago", weeks_ago), + weeks_ago); + else if (years_ago < 1) + return g_strdup_printf (ngettext ("%d month ago", + "%d months ago", months_ago), + months_ago); + else + return g_strdup_printf (ngettext ("%d year ago", + "%d years ago", years_ago), + years_ago); +} diff --git a/src/gs-common.h b/src/gs-common.h index 63c8e86bfc1714956f5b3adaef1937b1e859c299..cbd6dc61c044e1b1ea66a890b4e8d0ee4bdc66fc 100644 --- a/src/gs-common.h +++ b/src/gs-common.h @@ -47,5 +47,6 @@ gchar *gs_utils_build_unique_id_kind (AsComponentKind kind, gboolean gs_utils_list_has_component_fuzzy (GsAppList *list, GsApp *app); void gs_utils_reboot_notify (GsAppList *list); +gchar *gs_utils_time_to_string (gint64 unix_time_seconds); G_END_DECLS diff --git a/src/gs-details-page.c b/src/gs-details-page.c index 0dd4fa29714786f948f82fbec5da928dddabf417..099ff06d0d9aadad273cc4b001a345667223e00b 100644 --- a/src/gs-details-page.c +++ b/src/gs-details-page.c @@ -19,6 +19,8 @@ #include "gs-details-page.h" #include "gs-app-addon-row.h" +#include "gs-app-version-history-row.h" +#include "gs-app-version-history-dialog.h" #include "gs-description-box.h" #include "gs-history-dialog.h" #include "gs-origin-popover-row.h" @@ -111,16 +113,16 @@ struct _GsDetailsPage GtkWidget *label_details_size_download_value; GtkWidget *label_details_updated_title; GtkWidget *label_details_updated_value; - GtkWidget *label_details_version_title; - GtkWidget *label_details_version_value; - GtkWidget *label_details_released_title; - GtkWidget *label_details_released_value; GtkWidget *label_details_permissions_title; GtkWidget *button_details_permissions_value; GtkWidget *label_failed; GtkWidget *label_license_nonfree_details; GtkWidget *label_licenses_intro; GtkWidget *list_box_addons; + GtkWidget *list_box_version_history; + GtkWidget *box_version_history_frame; + GtkWidget *row_latest_version; + GtkWidget *version_history_button; GtkWidget *box_reviews; GtkWidget *box_details_screenshot_fallback; GtkWidget *histogram; @@ -1109,6 +1111,7 @@ gs_details_page_refresh_all (GsDetailsPage *self) guint64 user_integration_bf; gboolean show_support_box = FALSE; g_autofree gchar *origin = NULL; + GPtrArray *version_history; /* change widgets */ tmp = gs_app_get_name (self->app); @@ -1200,34 +1203,26 @@ gs_details_page_refresh_all (GsDetailsPage *self) gtk_widget_set_visible (self->label_details_channel_value, FALSE); } - /* set version */ - tmp = gs_app_get_version (self->app); - if (tmp != NULL){ - gtk_label_set_label (GTK_LABEL (self->label_details_version_value), tmp); + /* set version history */ + version_history = gs_app_get_version_history (self->app); + if (version_history == NULL) { + const char *version = gs_app_get_version (self->app); + if (version == NULL || *version == '\0') + gtk_widget_set_visible (self->box_version_history_frame, FALSE); + else + gs_app_version_history_row_set_info (GS_APP_VERSION_HISTORY_ROW (self->row_latest_version), + version, gs_app_get_release_date (self->app), NULL); } else { - /* TRANSLATORS: this is where the version is not known */ - gtk_label_set_label (GTK_LABEL (self->label_details_version_value), C_("version", "Unknown")); + AsRelease *latest_version = g_ptr_array_index (version_history, 0); + gs_app_version_history_row_set_info (GS_APP_VERSION_HISTORY_ROW (self->row_latest_version), + as_release_get_version (latest_version), + as_release_get_timestamp (latest_version), + as_release_get_description (latest_version)); } /* refresh size information */ gs_details_page_refresh_size (self); - /* set the released date */ - if (gs_app_get_release_date (self->app)) { - g_autoptr(GDateTime) dt = NULL; - g_autofree gchar *released_str = NULL; - - dt = g_date_time_new_from_unix_utc ((gint64) gs_app_get_release_date (self->app)); - released_str = g_date_time_format (dt, "%x"); - - gtk_label_set_label (GTK_LABEL (self->label_details_released_value), released_str); - gtk_widget_set_visible (self->label_details_released_title, TRUE); - gtk_widget_set_visible (self->label_details_released_value, TRUE); - } else { - gtk_widget_set_visible (self->label_details_released_title, FALSE); - gtk_widget_set_visible (self->label_details_released_value, FALSE); - } - /* set the updated date */ updated = gs_app_get_install_date (self->app); if (updated == GS_APP_INSTALL_DATE_UNSET) { @@ -1421,14 +1416,10 @@ gs_details_page_refresh_all (GsDetailsPage *self) case AS_COMPONENT_KIND_REPOSITORY: gtk_widget_set_visible (self->label_details_license_title, FALSE); gtk_widget_set_visible (self->box_details_license_value, FALSE); - gtk_widget_set_visible (self->label_details_version_title, FALSE); - gtk_widget_set_visible (self->label_details_version_value, FALSE); break; default: gtk_widget_set_visible (self->label_details_license_title, TRUE); gtk_widget_set_visible (self->box_details_license_value, TRUE); - gtk_widget_set_visible (self->label_details_version_title, TRUE); - gtk_widget_set_visible (self->label_details_version_value, TRUE); break; } @@ -1465,6 +1456,26 @@ list_sort_func (GtkListBoxRow *a, gs_app_get_name (a2)); } +static void +version_history_list_row_activated_cb (GtkListBox *list_box, + GtkListBoxRow *row, + GsDetailsPage *self) +{ + GtkWidget *dialog; + + /* Only the row with the arrow is clickable */ + if (GS_IS_APP_VERSION_HISTORY_ROW (row)) + return; + + dialog = gs_app_version_history_dialog_new (gs_shell_get_window (self->shell), + self->app); + gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog)); + + /* just destroy */ + g_signal_connect_swapped (dialog, "response", + G_CALLBACK (gtk_widget_destroy), dialog); +} + static void gs_details_page_addon_selected_cb (GsAppAddonRow *row, GParamSpec *pspec, GsDetailsPage *self); static void gs_details_page_addon_remove_cb (GsAppAddonRow *row, gpointer user_data); @@ -2882,14 +2893,14 @@ gs_details_page_class_init (GsDetailsPageClass *klass) gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_size_installed_value); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_updated_title); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_updated_value); - gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_version_title); - gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_version_value); - gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_released_title); - gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_released_value); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_permissions_title); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_permissions_value); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_failed); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, list_box_addons); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, list_box_version_history); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_version_history_frame); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, row_latest_version); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, version_history_button); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_reviews); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_fallback); gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, histogram); @@ -2948,6 +2959,12 @@ gs_details_page_init (GsDetailsPage *self) list_sort_func, self, NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (self->list_box_version_history), + list_header_func, + self, NULL); + g_signal_connect (self->list_box_version_history, "row-activated", + G_CALLBACK (version_history_list_row_activated_cb), self); + gtk_style_context_add_class (gtk_widget_get_style_context (self->button_details_permissions_value), "content-rating-permissions"); } diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui index a654049fa95a6c7ca859dffc3949f8d0a4afb80f..e4f65d527964de911d19ef746743a051bc816a5c 100644 --- a/src/gs-details-page.ui +++ b/src/gs-details-page.ui @@ -444,6 +444,60 @@ + + + True + in + fill + start + + + + True + none + + + + + + + + True + True + + + True + horizontal + center + 12 + 12 + + + True + 0 + 0.5 + Version History + + + + + True + go-next-symbolic + 6 + + + + + + + + + + False @@ -713,77 +767,6 @@ - - - True - Version - 0 - 0.5 - True - - - - 0 - 1 - - - - - True - True - True - 0.12.3 - True - end - 0 - 0.5 - - - - - - 1 - 1 - - - - - True - Released - 0 - 0.5 - True - - - - 0 - 2 - - - - - True - True - True - 0.12.3 - True - end - 0 - 0.5 - - - - - - 1 - 2 - - - True @@ -813,7 +796,7 @@ - + @@ -1262,7 +1245,6 @@ True - @@ -1275,7 +1257,6 @@ True - diff --git a/src/gs-updates-page.c b/src/gs-updates-page.c index 37919fc53e97f76d23d3ca5e20df088236273b73..0b51283ee245b17066e23510e40130e8599ad9d6 100644 --- a/src/gs-updates-page.c +++ b/src/gs-updates-page.c @@ -23,12 +23,6 @@ #include "gs-upgrade-banner.h" #include "gs-application.h" -#ifdef HAVE_GSETTINGS_DESKTOP_SCHEMAS -#include -#endif - -#include - typedef enum { GS_UPDATES_PAGE_FLAG_NONE = 0, GS_UPDATES_PAGE_FLAG_HAS_UPDATES = 1 << 0, @@ -176,91 +170,13 @@ _get_num_updates (GsUpdatesPage *self) return count; } -static GDateTime * -time_next_midnight (void) -{ - GDateTime *next_midnight; - GTimeSpan since_midnight; - g_autoptr(GDateTime) now = NULL; - - now = g_date_time_new_now_local (); - since_midnight = g_date_time_get_hour (now) * G_TIME_SPAN_HOUR + - g_date_time_get_minute (now) * G_TIME_SPAN_MINUTE + - g_date_time_get_second (now) * G_TIME_SPAN_SECOND + - g_date_time_get_microsecond (now); - next_midnight = g_date_time_add (now, G_TIME_SPAN_DAY - since_midnight); - - return next_midnight; -} - static gchar * gs_updates_page_last_checked_time_string (GsUpdatesPage *self) { -#ifdef HAVE_GSETTINGS_DESKTOP_SCHEMAS - GDesktopClockFormat clock_format; -#endif - const gchar *format_string; - gchar *time_string; - gboolean use_24h_time = FALSE; - gint64 tmp; - gint days_ago; - g_autoptr(GDateTime) last_checked = NULL; - g_autoptr(GDateTime) midnight = NULL; - - g_settings_get (self->settings, "check-timestamp", "x", &tmp); - if (tmp == 0) - return NULL; - last_checked = g_date_time_new_from_unix_local (tmp); - - midnight = time_next_midnight (); - days_ago = (gint) (g_date_time_difference (midnight, last_checked) / G_TIME_SPAN_DAY); - -#ifdef HAVE_GSETTINGS_DESKTOP_SCHEMAS - clock_format = g_settings_get_enum (self->desktop_settings, "clock-format"); - use_24h_time = (clock_format == G_DESKTOP_CLOCK_FORMAT_24H || self->ampm_available == FALSE); -#endif - - if (days_ago < 1) { // today - if (use_24h_time) { - /* TRANSLATORS: Time in 24h format */ - format_string = _("%R"); - } else { - /* TRANSLATORS: Time in 12h format */ - format_string = _("%l:%M %p"); - } - } else if (days_ago < 2) { // yesterday - if (use_24h_time) { - /* TRANSLATORS: This is the word "Yesterday" followed by a - time string in 24h format. i.e. "Yesterday, 14:30" */ - format_string = _("Yesterday, %R"); - } else { - /* TRANSLATORS: This is the word "Yesterday" followed by a - time string in 12h format. i.e. "Yesterday, 2:30 PM" */ - format_string = _("Yesterday, %l:%M %p"); - } - } else if (days_ago < 3) { - format_string = _("Two days ago"); - } else if (days_ago < 4) { - format_string = _("Three days ago"); - } else if (days_ago < 5) { - format_string = _("Four days ago"); - } else if (days_ago < 6) { - format_string = _("Five days ago"); - } else if (days_ago < 7) { - format_string = _("Six days ago"); - } else if (days_ago < 8) { - format_string = _("One week ago"); - } else if (days_ago < 15) { - format_string = _("Two weeks ago"); - } else { - /* TRANSLATORS: This is the date string with: day number, month name, year. - i.e. "25 May 2012" */ - format_string = _("%e %B %Y"); - } + gint64 last_checked; - time_string = g_date_time_format (last_checked, format_string); - - return time_string; + g_settings_get (self->settings, "check-timestamp", "x", &last_checked); + return gs_utils_time_to_string (last_checked); } static const gchar * @@ -1440,8 +1356,6 @@ gs_updates_page_class_init (GsUpdatesPageClass *klass) static void gs_updates_page_init (GsUpdatesPage *self) { - const char *ampm; - gtk_widget_init_template (GTK_WIDGET (self)); self->state = GS_UPDATES_PAGE_STATE_STARTUP; @@ -1454,9 +1368,6 @@ gs_updates_page_init (GsUpdatesPage *self) self->sizegroup_button = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->sizegroup_header = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); - ampm = nl_langinfo (AM_STR); - if (ampm != NULL && *ampm != '\0') - self->ampm_available = TRUE; } GsUpdatesPage * diff --git a/src/meson.build b/src/meson.build index 364b8638766265338acddab56834f785acbf0d04..49d708c6ef7497067340b0ff9c2b3443bc54322a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -17,6 +17,8 @@ gdbus_src = gnome.gdbus_codegen( gnome_software_sources = [ 'gs-app-addon-row.c', + 'gs-app-version-history-dialog.c', + 'gs-app-version-history-row.c', 'gs-application.c', 'gs-app-row.c', 'gs-app-tile.c', @@ -241,6 +243,7 @@ if get_option('tests') glib, gmodule, goa, + gsettings_desktop_schemas, gtk, json_glib, libgnomesoftware_dep,