[gnome-software] Add content rating UI for games



commit 573d0fe6a5d2c13817388ea2d6b447a98855e83c
Author: Richard Hughes <richard hughsie com>
Date:   Tue Sep 20 21:06:48 2016 +0100

    Add content rating UI for games

 src/gs-common.c         |  151 +++++++++++++++++++++++------------------------
 src/gs-common.h         |    3 +-
 src/gs-shell-details.c  |  127 +++++++++++++++++++++++++++++++++++++++
 src/gs-shell-details.ui |   93 +++++++++++++++++++++++++++++
 src/gtk-style.css       |   22 +++++++
 5 files changed, 319 insertions(+), 77 deletions(-)
---
diff --git a/src/gs-common.c b/src/gs-common.c
index 04a57d5..5e67945 100644
--- a/src/gs-common.c
+++ b/src/gs-common.c
@@ -311,223 +311,222 @@ gs_image_set_from_pixbuf (GtkImage *image, const GdkPixbuf *pixbuf)
        gs_image_set_from_pixbuf_with_scale (image, pixbuf, scale);
 }
 
-/**
- * gs_utils_get_content_rating:
- *
- * Note: These are strings marked for translation for comment.
- * This functionality is not currently used.
- **/
 const gchar *
-gs_utils_get_content_rating (void)
+gs_utils_content_rating_kv_to_str (const gchar *id, AsContentRatingValue value)
 {
+       guint i;
        struct {
                const gchar             *id;
                AsContentRatingValue     value;
                const gchar             *desc;
-       } content_rating_oars[] =  {
+       } tab[] =  {
        { "violence-cartoon",   AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating violence-cartoon", "None") },
+       _("No cartoon violence") },
        { "violence-cartoon",   AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Cartoon characters in unsafe situations") },
        { "violence-cartoon",   AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Cartoon characters in aggressive conflict") },
        { "violence-cartoon",   AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Graphic violence involving cartoon characters") },
        { "violence-fantasy",   AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating violence-fantasy", "None") },
+       _("No fantasy violence") },
        { "violence-fantasy",   AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Characters in unsafe situations easily distinguishable from reality") },
        { "violence-fantasy",   AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Characters in aggressive conflict easily distinguishable from reality") },
        { "violence-fantasy",   AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Graphic violence easily distinguishable from reality") },
        { "violence-realistic", AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating violence-realistic", "None") },
+       _("No realistic violence") },
        { "violence-realistic", AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Mildly realistic characters in unsafe situations") },
        { "violence-realistic", AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Depictions of realistic characters in aggressive conflict") },
        { "violence-realistic", AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Graphic violence involving realistic characters") },
        { "violence-bloodshed", AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating violence-bloodshed", "None") },
+       _("No bloodshed") },
        { "violence-bloodshed", AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Unrealistic bloodshed") },
        { "violence-bloodshed", AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Realistic bloodshed") },
        { "violence-bloodshed", AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Depictions of bloodshed and the mutilation of body parts") },
        { "violence-sexual",    AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating violence-sexual", "None") },
+       _("No sexual violence") },
        { "violence-sexual",    AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Rape or other violent sexual behavior") },
        { "drugs-alcohol",      AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating drugs-alcohol", "None") },
+       _("No references to alcohol") },
        { "drugs-alcohol",      AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("References to alcoholic beverages") },
        { "drugs-alcohol",      AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Use of alcoholic beverages") },
        { "drugs-narcotics",    AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating drugs-narcotics", "None") },
+       _("No references to illicit drugs") },
        { "drugs-narcotics",    AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("References to illicit drugs") },
        { "drugs-narcotics",    AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Use of illicit drugs") },
        { "drugs-tobacco",      AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("References to tobacco products") },
        { "drugs-tobacco",      AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Use of tobacco products") },
        { "sex-nudity",         AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating sex-nudity", "None") },
+       _("No nudity of any sort") },
        { "sex-nudity",         AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Brief artistic nudity") },
        { "sex-nudity",         AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Prolonged nudity") },
        { "sex-themes",         AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating sex-themes", "None") },
+       _("No references or depictions of sexual nature") },
        { "sex-themes",         AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Provocative references or depictions") },
        { "sex-themes",         AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Sexual references or depictions") },
        { "sex-themes",         AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Graphic sexual behavior") },
        { "language-profanity", AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating language-profanity", "None") },
+       _("No profanity of any kind") },
        { "language-profanity", AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Mild or infrequent use of profanity") },
        { "language-profanity", AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Moderate use of profanity") },
        { "language-profanity", AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Strong or frequent use of profanity") },
        { "language-humor",     AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating language-humor", "None") },
+       _("No innappropriate humor") },
        { "language-humor",     AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Slapstick humor") },
        { "language-humor",     AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Vulgar or bathroom humor") },
        { "language-humor",     AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Mature or sexual humor") },
        { "language-discrimination", AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating language-discrimination", "None") },
+       _("No discriminatory language of any kind") },
        { "language-discrimination", AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Negativity towards a specific group of people") },
        { "language-discrimination", AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Discrimination designed to cause emotional harm") },
        { "language-discrimination", AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Explicit discrimination based on gender, sexuality, race or religion") },
        { "money-advertising", AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating money-advertising", "None") },
+       _("No advertising of any kind") },
        { "money-advertising", AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Product placement") },
        { "money-advertising", AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Explicit references to specific brands or trademarked products") },
        { "money-advertising", AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Players are encouraged to purchase specific real-world items") },
        { "money-gambling",     AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating money-gambling", "None") },
+       _("No gambling of any kind") },
        { "money-gambling",     AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Gambling on random events using tokens or credits") },
        { "money-gambling",     AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Gambling using \"play\" money") },
        { "money-gambling",     AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Gambling using real money") },
        { "money-purchasing",   AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating money-purchasing", "None") },
+       _("No ability to spend money") },
        { "money-purchasing",   AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Ability to spend real money in-game") },
        { "social-chat",        AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating social-chat", "None") },
+       _("No way to chat with other players") },
        { "social-chat",        AS_CONTENT_RATING_VALUE_MILD,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Player-to-player game interactions without chat functionality") },
        { "social-chat",        AS_CONTENT_RATING_VALUE_MODERATE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Player-to-player preset interactions without chat functionality") },
        { "social-chat",        AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Uncontrolled chat functionality between players") },
        { "social-audio",       AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating social-audio", "None") },
+       _("No way to talk with other players") },
        { "social-audio",       AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Uncontrolled audio or video chat functionality between players") },
        { "social-contacts",    AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating social-contacts", "None") },
+       _("No sharing of social network usernames or email addresses") },
        { "social-contacts",    AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Sharing social network usernames or email addresses") },
        { "social-info",        AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating social-info", "None") },
+       _("No sharing of user information with 3rd parties") },
        { "social-info",        AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Sharing user information with 3rd parties") },
        { "social-location",    AS_CONTENT_RATING_VALUE_NONE,
        /* TRANSLATORS: content rating description */
-       C_("content rating social-location", "None") },
+       _("No sharing of physical location to other users") },
        { "social-location",    AS_CONTENT_RATING_VALUE_INTENSE,
-       /* TRANSLATORS: content rating description: comments welcome */
+       /* TRANSLATORS: content rating description */
        _("Sharing physical location to other users") },
        { NULL, 0, NULL } };
-       return content_rating_oars[0].desc;
+       for (i = 0; tab[i].id != NULL; i++) {
+               if (g_strcmp0 (tab[i].id, id) == 0 && tab[i].value == value)
+                       return tab[i].desc;
+       }
+       return NULL;
 }
 
 gboolean
diff --git a/src/gs-common.h b/src/gs-common.h
index e7e464d..631b9c9 100644
--- a/src/gs-common.h
+++ b/src/gs-common.h
@@ -48,7 +48,8 @@ void  gs_image_set_from_pixbuf_with_scale     (GtkImage               *image,
 void   gs_image_set_from_pixbuf                (GtkImage               *image,
                                                 const GdkPixbuf        *pixbuf);
 
-const gchar    *gs_utils_get_content_rating    (void);
+const gchar    *gs_utils_content_rating_kv_to_str (const gchar *id,
+                                               AsContentRatingValue value);
 
 const gchar    *gs_user_agent                  (void);
 gboolean        gs_utils_is_current_desktop    (const gchar    *name);
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index ffb9d98..ed4f3e0 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -134,6 +134,12 @@ struct _GsShellDetails
        GtkWidget               *popover_license_free;
        GtkWidget               *popover_license_nonfree;
        GtkWidget               *popover_license_unknown;
+       GtkWidget               *popover_content_rating;
+       GtkWidget               *label_content_rating_title;
+       GtkWidget               *label_content_rating_message;
+       GtkWidget               *label_content_rating_none;
+       GtkWidget               *button_details_rating_value;
+       GtkWidget               *label_details_rating_title;
 };
 
 G_DEFINE_TYPE (GsShellDetails, gs_shell_details, GS_TYPE_PAGE)
@@ -1304,6 +1310,44 @@ gs_shell_details_app_refine2 (GsShellDetails *self)
 }
 
 static void
+gs_shell_details_content_rating_set_css (GtkWidget *widget, guint age)
+{
+       GtkStyleContext *ctx = gtk_widget_get_style_context (widget);
+       gtk_style_context_remove_class (ctx, "content-rating-adult");
+       gtk_style_context_remove_class (ctx, "content-rating-parental-guidance");
+       if (age >= 18) {
+               gtk_style_context_add_class (ctx, "content-rating-adult");
+               return;
+       }
+       if (age >= 5) {
+               gtk_style_context_add_class (ctx, "content-parental-guidance");
+               return;
+       }
+}
+
+static void
+gs_shell_details_refresh_content_rating (GsShellDetails *self)
+{
+       AsContentRating *content_rating;
+       guint age = 0;
+
+       /* only show the button if a game and has a content rating */
+       content_rating = gs_app_get_content_rating (self->app);
+       if (content_rating != NULL)
+               age = as_content_rating_get_minimum_age (content_rating);
+       if (age > 3) {
+               g_autofree gchar *tmp = g_strdup_printf ("%u+", age);
+               gtk_button_set_label (GTK_BUTTON (self->button_details_rating_value), tmp);
+               gtk_widget_set_visible (self->button_details_rating_value, TRUE);
+               gtk_widget_set_visible (self->label_details_rating_title, TRUE);
+               gs_shell_details_content_rating_set_css (self->button_details_rating_value, age);
+       } else {
+               gtk_widget_set_visible (self->button_details_rating_value, FALSE);
+               gtk_widget_set_visible (self->label_details_rating_title, FALSE);
+       }
+}
+
+static void
 gs_shell_details_app_refine_cb (GObject *source,
                                GAsyncResult *res,
                                gpointer user_data)
@@ -1341,6 +1385,7 @@ gs_shell_details_app_refine_cb (GObject *source,
        gs_shell_details_refresh_addons (self);
        gs_shell_details_refresh_reviews (self);
        gs_shell_details_refresh_all (self);
+       gs_shell_details_refresh_content_rating (self);
        gs_shell_details_set_state (self, GS_SHELL_DETAILS_STATE_READY);
 
        /* do 2nd stage refine */
@@ -1720,6 +1765,79 @@ gs_shell_details_more_reviews_button_cb (GtkWidget *widget, GsShellDetails *self
        gtk_widget_set_visible (self->button_more_reviews, FALSE);
 }
 
+static void
+gs_shell_details_content_rating_button_cb (GtkWidget *widget, GsShellDetails *self)
+{
+       AsContentRating *cr;
+       AsContentRatingValue value_bad = AS_CONTENT_RATING_VALUE_NONE;
+       const gchar *tmp;
+       guint i, j;
+       g_autoptr(GString) str = g_string_new (NULL);
+       struct {
+               const gchar *ids[5];    /* ordered inside from worst to best */
+       } id_map[] = {
+               { "violence-bloodshed",
+                 "violence-realistic",
+                 "violence-fantasy",
+                 "violence-cartoon", NULL },
+               { "violence-sexual", NULL },
+               { "drugs-alcohol", NULL },
+               { "drugs-narcotics", NULL },
+               { "sex-nudity", NULL },
+               { "sex-themes", NULL },
+               { "language-profanity", NULL },
+               { "language-humor", NULL },
+               { "language-discrimination", NULL },
+               { "money-advertising", NULL },
+               { "money-gambling", NULL },
+               { "money-purchasing", NULL },
+               { "social-audio",
+                 "social-chat",
+                 "social-contacts",
+                 "social-info", NULL },
+               { "social-location", NULL },
+               { NULL }
+       };
+
+       /* get the worst thing */
+       cr = gs_app_get_content_rating (self->app);
+       if (cr == NULL)
+               return;
+       for (j = 0; id_map[j].ids[0] != NULL; j++) {
+               for (i = 0; id_map[j].ids[i] != NULL; i++) {
+                       AsContentRatingValue value;
+                       value = as_content_rating_get_value (cr, id_map[j].ids[i]);
+                       if (value > value_bad)
+                               value_bad = value;
+               }
+       }
+
+       /* get the content rating description for the worst things about the app */
+       for (j = 0; id_map[j].ids[0] != NULL; j++) {
+               for (i = 0; id_map[j].ids[i] != NULL; i++) {
+                       AsContentRatingValue value;
+                       value = as_content_rating_get_value (cr, id_map[j].ids[i]);
+                       if (value < value_bad)
+                               continue;
+                       tmp = gs_utils_content_rating_kv_to_str (id_map[j].ids[i], value);
+                       g_string_append_printf (str, "• %s\n", tmp);
+                       break;
+               }
+       }
+       if (str->len > 0)
+               g_string_truncate (str, str->len - 1);
+
+       /* enable the details if there are any */
+       gtk_label_set_label (GTK_LABEL (self->label_content_rating_message), str->str);
+       gtk_widget_set_visible (self->label_content_rating_title, str->len > 0);
+       gtk_widget_set_visible (self->label_content_rating_message, str->len > 0);
+       gtk_widget_set_visible (self->label_content_rating_none, str->len == 0);
+
+       /* show popover */
+       gtk_popover_set_relative_to (GTK_POPOVER (self->popover_content_rating), widget);
+       gtk_widget_show (self->popover_content_rating);
+}
+
 static gboolean
 gs_shell_details_activate_link_cb (GtkLabel *label,
                                   const gchar *uri,
@@ -1890,6 +2008,9 @@ gs_shell_details_setup (GsShellDetails *self,
        g_signal_connect (self->button_more_reviews, "clicked",
                          G_CALLBACK (gs_shell_details_more_reviews_button_cb),
                          self);
+       g_signal_connect (self->button_details_rating_value, "clicked",
+                         G_CALLBACK (gs_shell_details_content_rating_button_cb),
+                         self);
        g_signal_connect (self->label_details_updated_value, "activate-link",
                          G_CALLBACK (gs_shell_details_history_cb),
                          self);
@@ -2033,6 +2154,12 @@ gs_shell_details_class_init (GsShellDetailsClass *klass)
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, popover_license_unknown);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_license_nonfree_details);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_licenses_intro);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, popover_content_rating);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_content_rating_title);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_content_rating_message);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_content_rating_none);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, button_details_rating_value);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_rating_title);
 }
 
 static void
diff --git a/src/gs-shell-details.ui b/src/gs-shell-details.ui
index b6f03d2..3b25610 100644
--- a/src/gs-shell-details.ui
+++ b/src/gs-shell-details.ui
@@ -145,6 +145,7 @@
                                 <property name="position">1</property>
                               </packing>
                             </child>
+
                             <child>
                               <object class="GtkBox" id="star_box">
                                 <property name="visible">True</property>
@@ -184,6 +185,7 @@
                                 <property name="position">2</property>
                               </packing>
                             </child>
+
                           </object>
                           <packing>
                             <property name="expand">False</property>
@@ -798,6 +800,45 @@
                                     <property name="top_attach">0</property>
                                   </packing>
                                 </child>
+
+                                <child>
+                                  <object class="GtkLabel" id="label_details_rating_title">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="label" translatable="yes">Age Rating</property>
+                                    <property name="xalign">0</property>
+                                    <property name="yalign">0.5</property>
+                                    <property name="vexpand">True</property>
+                                    <style>
+                                      <class name="dim-label"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="left_attach">0</property>
+                                    <property name="top_attach">8</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkButton" id="button_details_rating_value">
+                                    <property name="use_underline">True</property>
+                                    <property name="label">18</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="vexpand">False</property>
+                                    <property name="halign">start</property>
+                                    <style>
+                                      <class name="content-rating"/>
+                                    </style>
+                                    <accessibility>
+                                      <relation type="labelled-by" target="label_details_version_title"/>
+                                    </accessibility>
+                                  </object>
+                                  <packing>
+                                    <property name="left_attach">1</property>
+                                    <property name="top_attach">8</property>
+                                  </packing>
+                                </child>
+
                                 <child>
                                   <object class="GtkLabel" id="label_details_updated_title">
                                     <property name="visible">True</property>
@@ -1503,4 +1544,56 @@
       </object>
     </child>
   </object>
+
+  <object class="GtkPopover" id="popover_content_rating">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="box_content_rating">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">24</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="label_content_rating_title">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">The application was rated this way because it 
features:</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_content_rating_message">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label">• Use of alcoholic beverages</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_content_rating_none">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">No details were available for this rating.</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+
 </interface>
diff --git a/src/gtk-style.css b/src/gtk-style.css
index 143e45b..45f2ebc 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -66,6 +66,28 @@
        color: #ffffff;
 }
 
+.content-rating {
+       outline-offset: 0;
+       background-image: none;
+       background-color: #dbdbdb;
+       border-image: none;
+       border-radius: 4px;
+       border-width: 0px;
+       padding: 1px 9px;
+       box-shadow: none;
+       text-shadow: none;
+}
+
+.content-rating-adult {
+       color: #ffffff;
+       background-color: #ee2222;
+}
+
+.content-rating-parental-guidance {
+       color: #ffffff;
+       background-color: #4e9a06;
+}
+
 .details-license-free, 
 .details-license-free label,
 .details-license-free:backdrop label {


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