[gnome-control-center] user-accounts: Implement new designed fingerprint dialog



commit d517a092aacbf678c0dfeed4d78df42a48cdd19a
Author: Marco Trevisan (Treviño) <mail 3v1n0 net>
Date:   Fri Apr 10 16:40:27 2020 +0200

    user-accounts: Implement new designed fingerprint dialog
    
    Implement the new designed interface for fingerprint enrollment, so that the
    dialog is now based on a stack of views:
     - A list of devices to choose (shown only if multiple are available)
     - A gallery of enrolled prints available where manage them
     - An enrollment progress view when enrolling a new finger
    
    Move part of the logic into a new FingerprintManager (to manage gdbus proxies
    generated via gdbus-codegen) that is created when configuring the current
    user and that tracks the devices states, while move most of the UI into a new
    CcFingerprintDialog that does all the operations in async way.
    
    Due to fprintd lack of APIs, there are few features missing, compared to
    the final design (none is a regression):
     - Identify the finger when the enroll dialog is visible
     - Delete a single fingerprint
     - Highlight the finger when the sensor is touched during enrollment
     - Add customized labels to fingerprints
     - Devices hotpluging
    
    However most of the code has been written considering these, and so they could
    be easily implemented in future re-iterations once newer APIs are defined for
    such bits.
    
    Closes https://gitlab.gnome.org/Teams/Design/settings-mockups/-/issues/18

 panels/user-accounts/cc-fingerprint-dialog.c       | 1438 ++++++++++++++++++++
 panels/user-accounts/cc-fingerprint-dialog.h       |   37 +
 panels/user-accounts/cc-fingerprint-dialog.ui      |  461 +++++++
 panels/user-accounts/cc-user-panel.c               |   23 +-
 panels/user-accounts/data/account-fingerprint.ui   |  238 ----
 .../user-accounts/data/cc-fingerprint-dialog.css   |   61 +
 .../fingerprint-detection-complete-symbolic.svg    |    3 +
 .../data/icons/fingerprint-detection-symbolic.svg  |    3 +
 .../fingerprint-detection-warning-symbolic.svg     |    3 +
 .../user-accounts/data/icons/left-index-finger.png |  Bin 1515 -> 0 bytes
 .../data/icons/left-little-finger.png              |  Bin 1500 -> 0 bytes
 .../data/icons/left-middle-finger.png              |  Bin 1483 -> 0 bytes
 .../user-accounts/data/icons/left-ring-finger.png  |  Bin 1512 -> 0 bytes
 panels/user-accounts/data/icons/left-thumb.png     |  Bin 1512 -> 0 bytes
 panels/user-accounts/data/icons/print_error.png    |  Bin 4160 -> 0 bytes
 panels/user-accounts/data/icons/print_ok.png       |  Bin 3677 -> 0 bytes
 .../data/icons/right-index-finger.png              |  Bin 1506 -> 0 bytes
 .../data/icons/right-little-finger.png             |  Bin 1479 -> 0 bytes
 .../data/icons/right-middle-finger.png             |  Bin 1468 -> 0 bytes
 .../user-accounts/data/icons/right-ring-finger.png |  Bin 1506 -> 0 bytes
 panels/user-accounts/data/icons/right-thumb.png    |  Bin 1486 -> 0 bytes
 panels/user-accounts/meson.build                   |   20 +-
 panels/user-accounts/um-fingerprint-dialog.c       |  778 -----------
 panels/user-accounts/um-fingerprint-dialog.h       |   27 -
 panels/user-accounts/user-accounts.gresource.xml   |   21 +-
 po/POTFILES.in                                     |    4 +-
 26 files changed, 2030 insertions(+), 1087 deletions(-)
---
diff --git a/panels/user-accounts/cc-fingerprint-dialog.c b/panels/user-accounts/cc-fingerprint-dialog.c
new file mode 100644
index 000000000..f3f5229c8
--- /dev/null
+++ b/panels/user-accounts/cc-fingerprint-dialog.c
@@ -0,0 +1,1438 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Authors: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#include <glib/gi18n.h>
+#include <cairo/cairo.h>
+
+#include "cc-fingerprint-dialog.h"
+
+#include "cc-fingerprint-manager.h"
+#include "cc-fprintd-generated.h"
+#include "cc-list-row.h"
+
+#include "config.h"
+
+/* Translate fprintd strings */
+#define TR(s) dgettext ("fprintd", s)
+#include "fingerprint-strings.h"
+
+struct _CcFingerprintDialog
+{
+  GtkWindow parent_instance;
+
+  GtkButton      *back_button;
+  GtkButton      *cancel_button;
+  GtkButton      *delete_prints_button;
+  GtkButton      *done_button;
+  GtkContainer   *add_print_popover_box;
+  GtkEntry       *enroll_print_entry;
+  GtkFlowBox     *prints_gallery;
+  GtkHeaderBar   *titlebar;
+  GtkImage       *enroll_result_image;
+  GtkLabel       *enroll_message;
+  GtkLabel       *enroll_result_message;
+  GtkLabel       *infobar_error;
+  GtkLabel       *title;
+  GtkListBox     *devices_list;
+  GtkPopoverMenu *add_print_popover;
+  GtkPopoverMenu *print_popover;
+  GtkSpinner     *spinner;
+  GtkStack       *stack;
+  GtkWidget      *add_print_icon;
+  GtkWidget      *delete_confirmation_infobar;
+  GtkWidget      *device_selector;
+  GtkWidget      *enroll_print_bin;
+  GtkWidget      *enroll_result_icon;
+  GtkWidget      *enrollment_view;
+  GtkWidget      *error_infobar;
+  GtkWidget      *no_devices_found;
+  GtkWidget      *prints_manager;
+
+  CcFingerprintManager *manager;
+  CcFprintdDevice      *device;
+  gboolean              device_claimed;
+  gulong                device_signal_id;
+  gulong                device_name_owner_id;
+  GCancellable         *cancellable;
+  GStrv                 enrolled_fingers;
+  const char           *enrolling_finger;
+  guint                 enroll_stages_passed;
+  guint                 enroll_stage_passed_id;
+  gdouble               enroll_progress;
+};
+
+/* TODO - fprintd and API changes required:
+  - Identify the finger when the enroll dialog is visible
+    + Only if device supports identification
+      · And only in such case support enrolling more than one finger
+  - Delete a single fingerprint | and remove the "Delete all" button
+  - Highlight the finger when the sensor is touched during enrollment
+  - Add customized labels to fingerprints
+  - Devices hotplug (object manager)
+ */
+
+G_DEFINE_TYPE (CcFingerprintDialog, cc_fingerprint_dialog, GTK_TYPE_WINDOW)
+
+enum {
+  PROP_0,
+  PROP_MANAGER,
+  N_PROPS
+};
+
+#define N_VALID_FINGERS G_N_ELEMENTS (FINGER_IDS) - 1
+/* The order of the fingers here will affect the UI order */
+const char * FINGER_IDS[] = {
+  "right-index-finger",
+  "left-index-finger",
+  "right-thumb",
+  "right-middle-finger",
+  "right-ring-finger",
+  "right-little-finger",
+  "left-thumb",
+  "left-middle-finger",
+  "left-ring-finger",
+  "left-little-finger",
+  "any",
+};
+
+typedef enum {
+  ENROLL_STATE_NORMAL,
+  ENROLL_STATE_SUCCESS,
+  ENROLL_STATE_WARNING,
+  ENROLL_STATE_ERROR,
+  ENROLL_STATE_COMPLETED,
+  N_ENROLL_STATES,
+} EnrollState;
+
+const char * ENROLL_STATE_CLASSES[N_ENROLL_STATES] = {
+  "",
+  "success",
+  "warning",
+  "error",
+  "completed",
+};
+
+static GParamSpec *properties[N_PROPS];
+
+CcFingerprintDialog *
+cc_fingerprint_dialog_new (CcFingerprintManager *manager)
+{
+  return g_object_new (CC_TYPE_FINGERPRINT_DIALOG,
+                       "fingerprint-manager", manager,
+                       NULL);
+}
+
+static void
+disconnect_device_signals (CcFingerprintDialog *self)
+{
+  if (!self->device)
+    return;
+
+  if (self->device_signal_id)
+    {
+      g_signal_handler_disconnect (self->device, self->device_signal_id);
+      self->device_signal_id = 0;
+    }
+
+  if (self->device_name_owner_id)
+    {
+      g_signal_handler_disconnect (self->device, self->device_name_owner_id);
+      self->device_name_owner_id = 0;
+    }
+}
+
+static void
+cc_fingerprint_dialog_dispose (GObject *object)
+{
+  CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
+
+  g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove);
+
+  if (self->device && self->device_claimed)
+    {
+      disconnect_device_signals (self);
+
+      if (self->enrolling_finger)
+        cc_fprintd_device_call_enroll_stop_sync (self->device, NULL, NULL);
+      cc_fprintd_device_call_release (self->device, NULL, NULL, NULL);
+    }
+
+  g_clear_object (&self->manager);
+  g_clear_object (&self->device);
+  g_clear_pointer (&self->enrolled_fingers, g_strfreev);
+
+  g_cancellable_cancel (self->cancellable);
+  g_clear_object (&self->cancellable);
+
+  G_OBJECT_CLASS (cc_fingerprint_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_fingerprint_dialog_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, self->manager);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_fingerprint_dialog_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_set_object (&self->manager, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+notify_error (CcFingerprintDialog *self,
+              const char          *error_message)
+{
+  if (error_message)
+    gtk_label_set_label (self->infobar_error, error_message);
+
+  gtk_widget_set_visible (self->error_infobar, error_message != NULL);
+}
+
+static gboolean
+fingerprint_icon_draw (GtkWidget *widget,
+                       cairo_t   *cr,
+                       gdouble   *progress_data)
+{
+  gdouble progress = 0.0f;
+
+  if (progress_data)
+    progress = *progress_data;
+
+  if (G_APPROX_VALUE (progress, 0.f, FLT_EPSILON) || progress > 1)
+    return FALSE;
+
+  GTK_WIDGET_GET_CLASS (widget)->draw (widget, cr);
+
+  if (progress > 0)
+    {
+      g_autoptr(GdkRGBA) outline_color = NULL;
+      GtkStyleContext *context;
+      GtkStateFlags state;
+      int outline_width;
+      int outline_offset;
+      int width;
+      int height;
+      int radius;
+      int delta;
+
+      context = gtk_widget_get_style_context (widget);
+      gtk_style_context_save (context);
+
+      state = gtk_style_context_get_state (context);
+
+      gtk_style_context_add_class (context, "progress");
+      gtk_style_context_get (context, state,
+                             "outline-width", &outline_width,
+                             "outline-offset", &outline_offset,
+                             "outline-color", &outline_color,
+                             NULL);
+
+      width = gtk_widget_get_allocated_width (widget);
+      height = gtk_widget_get_allocated_height (widget);
+      radius = MIN (width / 2, height / 2) + outline_offset;
+      delta = radius - outline_width / 2;
+
+      cairo_arc (cr, width / 2., height / 2., delta,
+                 1.5 * G_PI, (1.5 + progress * 2) * G_PI);
+      gdk_cairo_set_source_rgba (cr, outline_color);
+
+      cairo_set_line_width (cr, MIN (outline_width, radius));
+      cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+      cairo_stroke (cr);
+
+      gtk_style_context_restore (context);
+    }
+
+  return TRUE;
+}
+
+static GtkWidget *
+fingerprint_icon_new (const char *icon_name,
+                      const char *label_text,
+                      GType       icon_widget_type,
+                      gpointer    progress_data,
+                      GtkWidget **out_icon,
+                      GtkWidget **out_label)
+{
+  GtkStyleContext *context;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *image;
+  GtkWidget *icon_widget;
+
+  g_return_val_if_fail (g_type_is_a (icon_widget_type, GTK_TYPE_WIDGET), NULL);
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+  gtk_widget_set_name (box, "fingerprint-box");
+  gtk_widget_set_halign (box, GTK_ALIGN_CENTER);
+
+  image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DND);
+
+  if (icon_widget_type == GTK_TYPE_IMAGE)
+    icon_widget = image;
+  else
+    icon_widget = g_object_new (icon_widget_type, NULL);
+
+  if (progress_data)
+    g_signal_connect (image, "draw", G_CALLBACK (fingerprint_icon_draw),
+                      progress_data);
+
+  if (g_type_is_a (icon_widget_type, GTK_TYPE_BUTTON))
+    {
+      gtk_button_set_image (GTK_BUTTON (icon_widget), image);
+      gtk_button_set_relief (GTK_BUTTON (icon_widget), GTK_RELIEF_NONE);
+      gtk_widget_set_can_focus (icon_widget, FALSE);
+    }
+
+  gtk_widget_set_halign (icon_widget, GTK_ALIGN_CENTER);
+  gtk_widget_set_valign (icon_widget, GTK_ALIGN_CENTER);
+  gtk_widget_set_name (icon_widget, "fingerprint-image");
+
+  gtk_container_add (GTK_CONTAINER (box), icon_widget);
+
+  context = gtk_widget_get_style_context (icon_widget);
+  gtk_style_context_add_class (context, "fingerprint-image");
+
+  label = gtk_label_new_with_mnemonic (label_text);
+  gtk_container_add (GTK_CONTAINER (box), label);
+
+  context = gtk_widget_get_style_context (box);
+  gtk_style_context_add_class (context, "fingerprint-icon");
+
+  if (out_icon)
+    *out_icon = icon_widget;
+
+  if (out_label)
+    *out_label = label;
+
+  return box;
+}
+
+static GtkWidget *
+fingerprint_menu_button (const char *icon_name,
+                         const char *label_text)
+{
+  GtkWidget *flowbox_child;
+  GtkWidget *button;
+  GtkWidget *label;
+  GtkWidget *box;
+
+  box = fingerprint_icon_new (icon_name, label_text, GTK_TYPE_MENU_BUTTON, NULL,
+                              &button, &label);
+
+  flowbox_child = gtk_flow_box_child_new ();
+  gtk_widget_set_focus_on_click (flowbox_child, FALSE);
+  gtk_widget_set_name (flowbox_child, "fingerprint-flowbox");
+
+  gtk_container_add (GTK_CONTAINER (flowbox_child), box);
+
+  g_object_set_data (G_OBJECT (flowbox_child), "button", button);
+  g_object_set_data (G_OBJECT (flowbox_child), "icon",
+                     gtk_button_get_image (GTK_BUTTON (button)));
+  g_object_set_data (G_OBJECT (flowbox_child), "label", label);
+  g_object_set_data (G_OBJECT (button), "flowbox-child", flowbox_child);
+
+  return flowbox_child;
+}
+
+static gboolean
+prints_visibility_filter (GtkFlowBoxChild *child,
+                          gpointer         user_data)
+{
+  CcFingerprintDialog *self = user_data;
+  const char *finger_id;
+
+  if (gtk_stack_get_visible_child (self->stack) != self->prints_manager)
+    return FALSE;
+
+  finger_id = g_object_get_data (G_OBJECT (child), "finger-id");
+
+  if (!finger_id)
+    return TRUE;
+
+  if (!self->enrolled_fingers)
+    return FALSE;
+
+  return g_strv_contains ((const gchar **) self->enrolled_fingers, finger_id);
+}
+
+static void
+update_prints_to_add_visibility (CcFingerprintDialog *self)
+{
+  g_autoptr(GList) print_buttons = NULL;
+  GList *l;
+  guint i;
+
+  print_buttons = gtk_container_get_children (self->add_print_popover_box);
+
+  for (i = 0, l = print_buttons; i < N_VALID_FINGERS && l; ++i, l = l->next)
+    {
+      GtkWidget *button = l->data;
+      gboolean enrolled;
+
+      enrolled = self->enrolled_fingers &&
+                 g_strv_contains ((const gchar **) self->enrolled_fingers,
+                                  FINGER_IDS[i]);
+
+      gtk_widget_set_visible (button, !enrolled);
+    }
+}
+
+static void
+update_prints_visibility (CcFingerprintDialog *self)
+{
+  update_prints_to_add_visibility (self);
+
+  gtk_flow_box_invalidate_filter (self->prints_gallery);
+}
+
+static void
+list_enrolled_cb (GObject      *object,
+                  GAsyncResult *res,
+                  gpointer      user_data)
+{
+  g_auto(GStrv) enrolled_fingers = NULL;
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+  guint n_enrolled_fingers = 0;
+
+  cc_fprintd_device_call_list_enrolled_fingers_finish (fprintd_device,
+                                                       &enrolled_fingers,
+                                                       res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  gtk_spinner_stop (self->spinner);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), TRUE);
+
+  if (self->device_claimed)
+    gtk_widget_set_sensitive (GTK_WIDGET (self->prints_manager), TRUE);
+
+  if (error)
+    {
+      g_autofree char *dbus_error = g_dbus_error_get_remote_error (error);
+
+      if (!dbus_error || !g_str_has_suffix (dbus_error, ".Error.NoEnrolledPrints"))
+        {
+          g_autofree char *error_message = NULL;
+
+          g_dbus_error_strip_remote_error (error);
+          error_message = g_strdup_printf (_("Failed to list fingerprints: %s"),
+                                           error->message);
+          g_warning ("Listing of fingerprints on device %s failed: %s",
+                     cc_fprintd_device_get_name (self->device), error->message);
+          notify_error (self, error_message);
+          return;
+        }
+    }
+  else
+    {
+      n_enrolled_fingers = g_strv_length (enrolled_fingers);
+    }
+
+  self->enrolled_fingers = g_steal_pointer (&enrolled_fingers);
+  gtk_flow_box_set_max_children_per_line (self->prints_gallery,
+                                          MIN (3, n_enrolled_fingers + 1));
+
+  update_prints_visibility (self);
+
+  if (n_enrolled_fingers == N_VALID_FINGERS)
+    gtk_widget_set_sensitive (self->add_print_icon, FALSE);
+
+  if (n_enrolled_fingers > 0)
+    gtk_widget_show (GTK_WIDGET (self->delete_prints_button));
+}
+
+static void
+update_prints_store (CcFingerprintDialog *self)
+{
+  ActUser *user;
+
+  g_assert_true (CC_FPRINTD_IS_DEVICE (self->device));
+
+  gtk_spinner_start (self->spinner);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), FALSE);
+  gtk_widget_hide (GTK_WIDGET (self->delete_prints_button));
+
+  g_clear_pointer (&self->enrolled_fingers, g_strfreev);
+
+  user = cc_fingerprint_manager_get_user (self->manager);
+  cc_fprintd_device_call_list_enrolled_fingers (self->device,
+                                                act_user_get_user_name (user),
+                                                self->cancellable,
+                                                list_enrolled_cb,
+                                                self);
+}
+
+static void
+delete_prints_cb (GObject      *object,
+                  GAsyncResult *res,
+                  gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+
+  cc_fprintd_device_call_delete_enrolled_fingers2_finish (fprintd_device, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  if (error)
+    {
+      g_autofree char *error_message = NULL;
+
+      g_dbus_error_strip_remote_error (error);
+      error_message = g_strdup_printf (_("Failed to delete saved fingerprints: %s"),
+                                       error->message);
+      g_warning ("Deletion of fingerprints on device %s failed: %s",
+                 cc_fprintd_device_get_name (self->device), error->message);
+      notify_error (self, error_message);
+    }
+
+  update_prints_store (self);
+  cc_fingerprint_manager_update_state (self->manager, NULL, NULL);
+}
+
+static void
+delete_enrolled_prints (CcFingerprintDialog *self)
+{
+  g_return_if_fail (self->device_claimed);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->prints_manager), FALSE);
+  gtk_spinner_start (self->spinner);
+
+  cc_fprintd_device_call_delete_enrolled_fingers2 (self->device,
+                                                   self->cancellable,
+                                                   delete_prints_cb,
+                                                   self);
+}
+
+static const char *
+get_finger_name (const char *finger_id)
+{
+  if (g_str_equal (finger_id, "left-thumb"))
+    return _("Left thumb");
+  if (g_str_equal (finger_id, "left-middle-finger"))
+    return _("Left middle finger");
+  if (g_str_equal (finger_id, "left-index-finger"))
+    return _("_Left index finger");
+  if (g_str_equal (finger_id, "left-ring-finger"))
+    return _("Left ring finger");
+  if (g_str_equal (finger_id, "left-little-finger"))
+    return _("Left little finger");
+  if (g_str_equal (finger_id, "right-thumb"))
+    return _("Right thumb");
+  if (g_str_equal (finger_id, "right-middle-finger"))
+    return _("Right middle finger");
+  if (g_str_equal (finger_id, "right-index-finger"))
+    return _("_Right index finger");
+  if (g_str_equal (finger_id, "right-ring-finger"))
+    return _("Right ring finger");
+  if (g_str_equal (finger_id, "right-little-finger"))
+    return _("Right little finger");
+
+  g_return_val_if_reached (_("Unknown Finger"));
+}
+
+static gboolean
+have_multiple_devices (CcFingerprintDialog *self)
+{
+  g_autoptr(GList) devices_rows = NULL;
+
+  devices_rows = gtk_container_get_children (GTK_CONTAINER (self->devices_list));
+
+  return devices_rows && devices_rows->next;
+}
+
+static void
+set_enroll_result_message (CcFingerprintDialog *self,
+                           EnrollState          enroll_state,
+                           const char          *message)
+{
+  GtkStyleContext *style_context;
+  const char *icon_name;
+  guint i;
+
+  g_return_if_fail (enroll_state >= 0 && enroll_state < N_ENROLL_STATES);
+
+  style_context = gtk_widget_get_style_context (self->enroll_result_icon);
+
+  switch (enroll_state)
+    {
+      case ENROLL_STATE_WARNING:
+      case ENROLL_STATE_ERROR:
+        icon_name = "fingerprint-detection-warning-symbolic";
+        break;
+      case ENROLL_STATE_COMPLETED:
+        icon_name = "fingerprint-detection-complete-symbolic";
+        break;
+      default:
+        icon_name = "fingerprint-detection-symbolic";
+    }
+
+  for (i = 0; i < N_ENROLL_STATES; ++i)
+    gtk_style_context_remove_class (style_context, ENROLL_STATE_CLASSES[i]);
+
+  gtk_style_context_add_class (style_context, ENROLL_STATE_CLASSES[enroll_state]);
+
+  gtk_image_set_from_icon_name (self->enroll_result_image, icon_name, GTK_ICON_SIZE_DND);
+  gtk_label_set_label (self->enroll_result_message, message);
+}
+
+static gboolean
+stage_passed_timeout_cb (gpointer user_data)
+{
+  CcFingerprintDialog *self = user_data;
+
+  set_enroll_result_message (self, ENROLL_STATE_NORMAL, NULL);
+  self->enroll_stage_passed_id = 0;
+
+  return FALSE;
+}
+
+static void
+handle_enroll_signal (CcFingerprintDialog *self,
+                      const char          *result,
+                      gboolean             done)
+{
+  gboolean completed;
+
+  g_return_if_fail (self->enrolling_finger);
+
+  g_debug ("Device enroll result message: %s, done: %d", result, done);
+
+  completed = g_str_equal (result, "enroll-completed");
+  g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove);
+
+  if (g_str_equal (result, "enroll-stage-passed") || completed)
+    {
+      guint enroll_stages;
+
+      enroll_stages = cc_fprintd_device_get_num_enroll_stages (self->device);
+
+      self->enroll_stages_passed++;
+
+      if (enroll_stages > 0)
+        self->enroll_progress =
+          MIN (1.0f, self->enroll_stages_passed / (double) enroll_stages);
+      else
+        g_warning ("The device %s requires an invalid number of enroll stages (%u)",
+                   cc_fprintd_device_get_name (self->device), enroll_stages);
+
+      g_debug ("Enroll state passed, %u/%u (%.2f%%)",
+               self->enroll_stages_passed, (guint) enroll_stages,
+               self->enroll_progress);
+
+      if (!completed)
+        {
+          set_enroll_result_message (self, ENROLL_STATE_SUCCESS, NULL);
+
+          self->enroll_stage_passed_id =
+            g_timeout_add (750, stage_passed_timeout_cb, self);
+        }
+      else
+        {
+          if (!G_APPROX_VALUE (self->enroll_progress, 1.0f, FLT_EPSILON))
+            {
+              g_warning ("Device marked enroll as completed, but progress is at %.2f",
+                         self->enroll_progress);
+              self->enroll_progress = 1.0f;
+            }
+        }
+    }
+  else if (!done)
+    {
+      const char *scan_type;
+      const char *message;
+      gboolean is_swipe;
+
+      scan_type = cc_fprintd_device_get_scan_type (self->device);
+      is_swipe = g_str_equal (scan_type, "swipe");
+
+      message = TR (enroll_result_str_to_msg (result, is_swipe));
+      set_enroll_result_message (self, ENROLL_STATE_NORMAL, message);
+    }
+
+  if (done)
+    {
+      if (completed)
+        {
+          /* TRANSLATORS: This is the message shown when the fingerprint
+           * enrollment has been completed successfully */
+          set_enroll_result_message (self, ENROLL_STATE_COMPLETED,
+                                     C_("Fingerprint enroll state", "Complete"));
+          gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), FALSE);
+          gtk_widget_set_sensitive (GTK_WIDGET (self->done_button), TRUE);
+          gtk_widget_grab_focus (GTK_WIDGET (self->done_button));
+        }
+      else
+        {
+          const char *message;
+
+          if (g_str_equal (result, "enroll-disconnected"))
+            message = _("Fingerprint device disconnected");
+          else if (g_str_equal (result, "enroll-data-full"))
+            message = _("Fingerprint device storage is full");
+          else
+            message = _("Failed to enroll new fingerprint");
+
+          self->enrolling_finger = NULL;
+          set_enroll_result_message (self, ENROLL_STATE_WARNING, message);
+        }
+    }
+}
+
+static void
+enroll_start_cb (GObject      *object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+
+  cc_fprintd_device_call_enroll_start_finish (fprintd_device, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  gtk_spinner_stop (self->spinner);
+
+  if (error)
+    {
+      g_autofree char *error_message = NULL;
+
+      self->enrolling_finger = NULL;
+
+      g_dbus_error_strip_remote_error (error);
+      error_message = g_strdup_printf (_("Failed to start enrollment: %s"),
+                                       error->message);
+      g_warning ("Enrollment on device %s failed: %s",
+                 cc_fprintd_device_get_name (self->device), error->message);
+      notify_error (self, error_message);
+
+      set_enroll_result_message (self, ENROLL_STATE_ERROR,
+                                 C_("Fingerprint enroll state",
+                                    "Failed to enroll new fingerprint"));
+      gtk_widget_set_sensitive (self->enrollment_view, FALSE);
+
+      return;
+    }
+}
+
+static void
+enroll_stop_cb (GObject      *object,
+                GAsyncResult *res,
+                gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+
+  cc_fprintd_device_call_enroll_stop_finish (fprintd_device, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  self->enrolling_finger = NULL;
+  gtk_spinner_stop (self->spinner);
+  gtk_widget_set_sensitive (self->enrollment_view, TRUE);
+  gtk_stack_set_visible_child (self->stack, self->prints_manager);
+
+  if (error)
+    {
+      g_autofree char *error_message = NULL;
+
+      g_dbus_error_strip_remote_error (error);
+      error_message = g_strdup_printf (_("Failed to stop enrollment: %s"),
+                                       error->message);
+      g_warning ("Stopping enrollment on device %s failed: %s",
+                 cc_fprintd_device_get_name (self->device), error->message);
+      notify_error (self, error_message);
+
+      return;
+    }
+
+  cc_fingerprint_manager_update_state (self->manager, NULL, NULL);
+}
+
+static void
+enroll_stop (CcFingerprintDialog *self)
+{
+  g_return_if_fail (self->enrolling_finger);
+
+  gtk_spinner_start (self->spinner);
+  gtk_widget_set_sensitive (self->enrollment_view, FALSE);
+  cc_fprintd_device_call_enroll_stop (self->device, self->cancellable,
+                                      enroll_stop_cb, self);
+}
+
+static char *
+get_enrollment_string (CcFingerprintDialog *self,
+                       const char          *finger_id)
+{
+  char *ret;
+  const char *scan_type;
+  const char *device_name;
+  gboolean is_swipe;
+
+  device_name = NULL;
+  scan_type = cc_fprintd_device_get_scan_type (self->device);
+  is_swipe = g_str_equal (scan_type, "swipe");
+
+  if (have_multiple_devices (self))
+    device_name = cc_fprintd_device_get_name (self->device);
+
+  ret = TR (finger_str_to_msg (finger_id, device_name, is_swipe));
+
+  if (ret)
+    return ret;
+
+  return g_strdup (_("Repeatedly lift and place your finger on the reader to enroll your fingerprint"));
+}
+
+static void
+enroll_finger (CcFingerprintDialog *self,
+               const char          *finger_id)
+{
+  g_auto(GStrv) tmp_finger_name = NULL;
+  g_autofree char *enroll_message = NULL;
+  g_autofree char *finger_name = NULL;
+
+  g_return_if_fail (finger_id);
+
+  self->enrolling_finger = finger_id;
+  self->enroll_progress = 0;
+  self->enroll_stages_passed = 0;
+
+  g_debug ("Enrolling finger %s", finger_id);
+
+  enroll_message = TR (get_enrollment_string (self, finger_id));
+  tmp_finger_name = g_strsplit (get_finger_name (finger_id), "_", -1);
+  finger_name = g_strjoinv ("", tmp_finger_name);
+
+  set_enroll_result_message (self, ENROLL_STATE_NORMAL, NULL);
+  gtk_stack_set_visible_child (self->stack, self->enrollment_view);
+  gtk_label_set_label (self->enroll_message, enroll_message);
+  gtk_entry_set_text (self->enroll_print_entry, finger_name);
+  gtk_spinner_start (self->spinner);
+
+  cc_fprintd_device_call_enroll_start (self->device, finger_id, self->cancellable,
+                                       enroll_start_cb, self);
+}
+
+static void
+populate_enrollment_view (CcFingerprintDialog *self)
+{
+  GtkStyleContext *style_context;
+
+  self->enroll_result_icon =
+    fingerprint_icon_new ("fingerprint-detection-symbolic",
+                          NULL,
+                          GTK_TYPE_IMAGE,
+                          &self->enroll_progress,
+                          (GtkWidget **) &self->enroll_result_image,
+                          (GtkWidget **) &self->enroll_result_message);
+
+  gtk_container_add (GTK_CONTAINER (self->enroll_print_bin), self->enroll_result_icon);
+
+  style_context = gtk_widget_get_style_context (self->enroll_result_icon);
+  gtk_style_context_add_class (style_context,  "enroll-status");
+
+  gtk_widget_show_all (self->enroll_print_bin);
+}
+
+static void
+reenroll_finger_cb (CcFingerprintDialog *self)
+{
+  GtkWidget *button;
+  GtkWidget *flowbox_child;
+  const char *finger_id;
+
+  button = gtk_popover_get_relative_to (GTK_POPOVER (self->print_popover));
+  flowbox_child = g_object_get_data (G_OBJECT (button), "flowbox-child");
+  finger_id = g_object_get_data (G_OBJECT (flowbox_child), "finger-id");
+
+  enroll_finger (self, finger_id);
+}
+
+static void
+on_print_activated_cb (GtkFlowBox          *flowbox,
+                       GtkFlowBoxChild     *child,
+                       CcFingerprintDialog *self)
+{
+  GtkWidget *selected_button;
+
+  selected_button = g_object_get_data (G_OBJECT (child), "button");
+  gtk_button_clicked (GTK_BUTTON (selected_button));
+}
+
+static void
+on_enroll_cb (CcFingerprintDialog *self,
+              GtkModelButton      *button)
+{
+  const char *finger_id;
+
+  finger_id = g_object_get_data (G_OBJECT (button), "finger-id");
+  enroll_finger (self, finger_id);
+}
+
+static void
+populate_add_print_popover (CcFingerprintDialog *self)
+{
+  guint i;
+
+  for (i = 0; i < N_VALID_FINGERS; ++i)
+    {
+      GtkWidget *finger_item;
+
+      finger_item = gtk_model_button_new ();
+      gtk_button_set_label (GTK_BUTTON (finger_item), get_finger_name (FINGER_IDS[i]));
+      gtk_button_set_use_underline (GTK_BUTTON (finger_item), TRUE);
+      g_object_set_data (G_OBJECT (finger_item), "finger-id", (gpointer) FINGER_IDS[i]);
+      gtk_container_add (self->add_print_popover_box, finger_item);
+
+      g_signal_connect_object (finger_item, "clicked", G_CALLBACK (on_enroll_cb),
+                               self, G_CONNECT_SWAPPED);
+    }
+}
+
+static void
+populate_prints_gallery (CcFingerprintDialog *self)
+{
+  const char *add_print_label;
+  GtkWidget *button;
+  GtkStyleContext *style_context;
+  guint i;
+
+  g_return_if_fail (!GTK_IS_WIDGET (self->add_print_icon));
+
+  for (i = 0; i < N_VALID_FINGERS; ++i)
+    {
+      GtkWidget *flowbox_child;
+
+      flowbox_child = fingerprint_menu_button ("fingerprint-detection-symbolic",
+                                               get_finger_name (FINGER_IDS[i]));
+
+      button = g_object_get_data (G_OBJECT (flowbox_child), "button");
+
+      gtk_menu_button_set_popover (GTK_MENU_BUTTON (button),
+                                   GTK_WIDGET (self->print_popover));
+      /* Move the popover on click, so we can just reuse the same instance */
+      g_signal_connect_object (button, "clicked",
+                               G_CALLBACK (gtk_popover_set_relative_to),
+                               self->print_popover, G_CONNECT_SWAPPED);
+
+      g_object_set_data (G_OBJECT (flowbox_child), "finger-id",
+                         (gpointer) FINGER_IDS[i]);
+
+      gtk_flow_box_insert (self->prints_gallery, flowbox_child, i);
+    }
+
+  /* TRANSLATORS: This is the label for the button to enroll a new finger */
+  add_print_label = _("Scan new fingerprint");
+  self->add_print_icon = fingerprint_menu_button ("list-add-symbolic",
+                                                  add_print_label);
+  style_context = gtk_widget_get_style_context (self->add_print_icon);
+  gtk_style_context_add_class (style_context, "fingerprint-print-add");
+
+  populate_add_print_popover (self);
+  button = g_object_get_data (G_OBJECT (self->add_print_icon), "button");
+  gtk_menu_button_set_popover (GTK_MENU_BUTTON (button),
+                               GTK_WIDGET (self->add_print_popover));
+
+  gtk_flow_box_insert (self->prints_gallery, self->add_print_icon, -1);
+  gtk_flow_box_set_max_children_per_line (self->prints_gallery, 1);
+
+  gtk_widget_show_all (GTK_WIDGET (self->prints_gallery));
+  gtk_flow_box_set_filter_func (self->prints_gallery, prints_visibility_filter,
+                                self, NULL);
+
+  update_prints_visibility (self);
+}
+
+static void
+release_device_cb (GObject      *object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+
+  cc_fprintd_device_call_release_finish (fprintd_device, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  if (error)
+    {
+      g_autofree char *error_message = NULL;
+
+      g_dbus_error_strip_remote_error (error);
+      error_message = g_strdup_printf (_("Failed to release fingerprint device %s: %s"),
+                                       cc_fprintd_device_get_name (self->device),
+                                       error->message);
+      g_warning ("%s", error_message);
+
+      notify_error (self, error_message);
+      return;
+    }
+
+  self->device_claimed = FALSE;
+}
+
+static void
+release_device (CcFingerprintDialog *self)
+{
+  if (!self->device || !self->device_claimed)
+    return;
+
+  disconnect_device_signals (self);
+
+  cc_fprintd_device_call_release (self->device,
+                                  self->cancellable,
+                                  release_device_cb,
+                                  self);
+}
+
+static void
+on_device_signal (CcFingerprintDialog *self,
+                  gchar               *sender_name,
+                  gchar               *signal_name,
+                  GVariant            *parameters,
+                  gpointer             user_data)
+{
+  if (g_str_equal (signal_name, "EnrollStatus"))
+    {
+      const char *result;
+      gboolean done;
+
+      if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sb)")))
+        {
+          g_warning ("Unexpected enroll parameters type %s",
+                     g_variant_get_type_string (parameters));
+          return;
+        }
+
+      g_variant_get (parameters, "(&sb)", &result, &done);
+      handle_enroll_signal (self, result, done);
+    }
+}
+
+static void claim_device (CcFingerprintDialog *self);
+
+static void
+on_device_owner_changed (CcFprintdDevice     *device,
+                         GParamSpec          *spec,
+                         CcFingerprintDialog *self)
+{
+  g_autofree char *name_owner = NULL;
+
+  name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (device));
+
+  if (!name_owner)
+    {
+      if (self->device_claimed)
+        {
+          disconnect_device_signals (self);
+
+          if (self->enrolling_finger)
+            {
+              set_enroll_result_message (self, ENROLL_STATE_ERROR,
+                                         C_("Fingerprint enroll state",
+                                            "Problem Reading Device"));
+              self->enrolling_finger = NULL;
+            }
+
+          self->device_claimed = FALSE;
+          claim_device (self);
+        }
+    }
+}
+
+static void
+claim_device_cb (GObject      *object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
+  CcFingerprintDialog *self = user_data;
+
+  cc_fprintd_device_call_claim_finish (fprintd_device, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  if (error)
+    {
+      g_autofree char *dbus_error = g_dbus_error_get_remote_error (error);
+      g_autofree char *error_message = NULL;
+
+      if (dbus_error && g_str_has_suffix (dbus_error, ".Error.AlreadyInUse"))
+        self->device_claimed = TRUE;
+
+      g_dbus_error_strip_remote_error (error);
+      error_message = g_strdup_printf (_("Failed to claim fingerprint device %s: %s"),
+                                       cc_fprintd_device_get_name (self->device),
+                                       error->message);
+      g_warning ("%s", error_message);
+      notify_error (self, error_message);
+      return;
+    }
+
+  gtk_widget_set_sensitive (self->prints_manager, TRUE);
+  self->device_claimed = TRUE;
+  self->device_signal_id = g_signal_connect_object (self->device, "g-signal",
+                                                    G_CALLBACK (on_device_signal),
+                                                    self, G_CONNECT_SWAPPED);
+  self->device_name_owner_id = g_signal_connect_object (self->device, "notify::g-name-owner",
+                                                        G_CALLBACK (on_device_owner_changed),
+                                                        self, 0);
+}
+
+static void
+claim_device (CcFingerprintDialog *self)
+{
+  ActUser *user;
+
+  user = cc_fingerprint_manager_get_user (self->manager);
+  gtk_widget_set_sensitive (self->prints_manager, FALSE);
+
+  cc_fprintd_device_call_claim (self->device,
+                                act_user_get_user_name (user),
+                                self->cancellable,
+                                claim_device_cb,
+                                self);
+}
+
+static void
+on_stack_child_changed (CcFingerprintDialog *self)
+{
+  GtkWidget *visible_child = gtk_stack_get_visible_child (self->stack);
+
+  g_debug ("Fingerprint dialog child changed: %s",
+           gtk_stack_get_visible_child_name (self->stack));
+
+  gtk_widget_hide (GTK_WIDGET (self->back_button));
+  gtk_widget_hide (GTK_WIDGET (self->cancel_button));
+  gtk_widget_hide (GTK_WIDGET (self->done_button));
+
+  gtk_header_bar_set_show_close_button (self->titlebar, TRUE);
+  gtk_flow_box_invalidate_filter (self->prints_gallery);
+
+  if (visible_child == self->prints_manager)
+    {
+      gtk_widget_set_visible (GTK_WIDGET (self->back_button),
+                              have_multiple_devices (self));
+      notify_error (self, NULL);
+      update_prints_store (self);
+
+      if (!self->device_claimed)
+        claim_device (self);
+    }
+  else if (visible_child == self->enrollment_view)
+    {
+      gtk_header_bar_set_show_close_button (self->titlebar, FALSE);
+
+      gtk_widget_show (GTK_WIDGET (self->cancel_button));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), TRUE);
+
+      gtk_widget_show (GTK_WIDGET (self->done_button));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->done_button), FALSE);
+    }
+  else
+    {
+      release_device (self);
+      g_clear_object (&self->device);
+    }
+}
+
+static void
+cc_fingerprint_dialog_init (CcFingerprintDialog *self)
+{
+  g_autoptr(GtkCssProvider) provider = NULL;
+
+  self->cancellable = g_cancellable_new ();
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  provider = gtk_css_provider_new ();
+  gtk_css_provider_load_from_resource (provider,
+                                       "/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.css");
+  gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+                                             GTK_STYLE_PROVIDER (provider),
+                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+  on_stack_child_changed (self);
+  g_signal_connect_object (self->stack, "notify::visible-child",
+                           G_CALLBACK (on_stack_child_changed), self,
+                           G_CONNECT_SWAPPED);
+
+  g_object_bind_property (self->stack, "visible-child-name",
+                          self->title, "label", G_BINDING_SYNC_CREATE);
+
+  populate_prints_gallery (self);
+  populate_enrollment_view (self);
+}
+
+static void
+select_device_row (CcFingerprintDialog *self,
+                   GtkListBoxRow       *row,
+                   GtkListBox          *listbox)
+{
+  CcFprintdDevice *device = g_object_get_data (G_OBJECT (row), "device");
+
+  g_return_if_fail (CC_FPRINTD_DEVICE (device));
+
+  g_set_object (&self->device, device);
+  gtk_stack_set_visible_child (self->stack, self->prints_manager);
+}
+
+static void
+on_devices_list (GObject      *object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+  g_autolist (CcFprintdDevice) fprintd_devices = NULL;
+  g_autoptr(GError) error = NULL;
+  CcFingerprintManager *fingerprint_manager = CC_FINGERPRINT_MANAGER (object);
+  CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (user_data);
+
+  fprintd_devices = cc_fingerprint_manager_get_devices_finish (fingerprint_manager,
+                                                               res, &error);
+  gtk_spinner_stop (self->spinner);
+
+  if (fprintd_devices == NULL)
+    {
+      if (error)
+        {
+          g_autofree char *error_message = NULL;
+
+          g_dbus_error_strip_remote_error (error);
+          error_message = g_strdup_printf (_("Failed to get fingerprint devices: %s"),
+                                           error->message);
+          g_warning ("%s", error_message);
+          notify_error (self, error_message);
+        }
+
+      gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->no_devices_found));
+    }
+  else if (fprintd_devices->next == NULL)
+    {
+      /* We have just one device... Skip devices selection */
+      self->device = g_object_ref (fprintd_devices->data);
+      gtk_stack_set_visible_child (self->stack, self->prints_manager);
+    }
+  else
+    {
+      GList *l;
+
+      for (l = fprintd_devices; l; l = l->next)
+        {
+          CcFprintdDevice *device = l->data;
+          CcListRow *device_row;
+
+          device_row = g_object_new (CC_TYPE_LIST_ROW,
+                                     "visible", TRUE,
+                                     "icon-name", "go-next-symbolic",
+                                     "title", cc_fprintd_device_get_name (device),
+                                     NULL);
+
+          gtk_list_box_insert (self->devices_list, GTK_WIDGET (device_row), -1);
+          g_object_set_data_full (G_OBJECT (device_row), "device",
+                                  g_object_ref (device), g_object_unref);
+        }
+
+      gtk_stack_set_visible_child (self->stack, self->device_selector);
+    }
+}
+
+static void
+cc_fingerprint_dialog_constructed (GObject *object)
+{
+  CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
+
+  bindtextdomain ("fprintd", GNOMELOCALEDIR);
+  bind_textdomain_codeset ("fprintd", "UTF-8");
+
+  gtk_spinner_start (self->spinner);
+  cc_fingerprint_manager_get_devices (self->manager, self->cancellable,
+                                      on_devices_list, self);
+}
+
+static void
+back_button_clicked_cb (CcFingerprintDialog *self)
+{
+  if (gtk_stack_get_visible_child (self->stack) == self->prints_manager)
+    {
+      notify_error (self, NULL);
+      gtk_stack_set_visible_child (self->stack, self->device_selector);
+      return;
+    }
+
+  g_return_if_reached ();
+}
+
+static void
+confirm_deletion_button_clicked_cb (CcFingerprintDialog *self)
+{
+  gtk_widget_hide (self->delete_confirmation_infobar);
+  delete_enrolled_prints (self);
+}
+
+static void
+cancel_deletion_button_clicked_cb (CcFingerprintDialog *self)
+{
+  gtk_widget_set_sensitive (self->prints_manager, TRUE);
+  gtk_widget_hide (self->delete_confirmation_infobar);
+}
+
+static void
+delete_prints_button_clicked_cb (CcFingerprintDialog *self)
+{
+  gtk_widget_set_sensitive (self->prints_manager, FALSE);
+  gtk_widget_show (self->delete_confirmation_infobar);
+}
+
+static void
+cancel_button_clicked_cb (CcFingerprintDialog *self)
+{
+  if (self->enrolling_finger)
+    {
+      g_cancellable_cancel (self->cancellable);
+      g_set_object (&self->cancellable, g_cancellable_new ());
+
+      g_debug ("Cancelling enroll operation");
+      enroll_stop (self);
+    }
+  else
+    {
+      gtk_stack_set_visible_child (self->stack, self->prints_manager);
+    }
+}
+
+static void
+done_button_clicked_cb (CcFingerprintDialog *self)
+{
+  g_return_if_fail (self->enrolling_finger);
+
+  g_debug ("Completeing enroll operation");
+  enroll_stop (self);
+}
+
+static void
+fingerprint_dialog_delete_cb (CcFingerprintDialog *self)
+{
+  cc_fingerprint_manager_update_state (self->manager, NULL, NULL);
+  gtk_widget_destroy (GTK_WIDGET (self));
+}
+
+static void
+cc_fingerprint_dialog_class_init (CcFingerprintDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+    "/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.ui");
+
+  object_class->constructed = cc_fingerprint_dialog_constructed;
+  object_class->dispose = cc_fingerprint_dialog_dispose;
+  object_class->get_property = cc_fingerprint_dialog_get_property;
+  object_class->set_property = cc_fingerprint_dialog_set_property;
+
+  properties[PROP_MANAGER] =
+    g_param_spec_object ("fingerprint-manager",
+                         "FingerprintManager",
+                         "The CC fingerprint manager",
+                         CC_TYPE_FINGERPRINT_MANAGER,
+                         G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover_box);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, back_button);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, cancel_button);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_confirmation_infobar);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_prints_button);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, device_selector);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, devices_list);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, done_button);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_message);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_bin);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enrollment_view);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, error_infobar);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, infobar_error);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, no_devices_found);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, print_popover);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_gallery);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_manager);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, spinner);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, stack);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, title);
+  gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, titlebar);
+
+  gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cancel_deletion_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, confirm_deletion_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, delete_prints_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, done_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, fingerprint_dialog_delete_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_print_activated_cb);
+  gtk_widget_class_bind_template_callback (widget_class, reenroll_finger_cb);
+  gtk_widget_class_bind_template_callback (widget_class, select_device_row);
+}
diff --git a/panels/user-accounts/cc-fingerprint-dialog.h b/panels/user-accounts/cc-fingerprint-dialog.h
new file mode 100644
index 000000000..9afac0ba7
--- /dev/null
+++ b/panels/user-accounts/cc-fingerprint-dialog.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Authors: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "cc-fingerprint-manager.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_FINGERPRINT_DIALOG (cc_fingerprint_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (CcFingerprintDialog, cc_fingerprint_dialog,
+                      CC, FINGERPRINT_DIALOG, GtkWindow)
+
+CcFingerprintDialog *cc_fingerprint_dialog_new (CcFingerprintManager *manager);
+
+G_END_DECLS
diff --git a/panels/user-accounts/cc-fingerprint-dialog.ui b/panels/user-accounts/cc-fingerprint-dialog.ui
new file mode 100644
index 000000000..076625740
--- /dev/null
+++ b/panels/user-accounts/cc-fingerprint-dialog.ui
@@ -0,0 +1,461 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcFingerprintDialog" parent="GtkWindow">
+    <style>
+      <class name="fingerprint" />
+    </style>
+    <property name="name">fingerprint-dialog</property>
+    <property name="title" translatable="yes">Fingerprint Manager</property>
+    <property name="type-hint">dialog</property>
+    <property name="window-position">center-on-parent</property>
+    <property name="destroy-with-parent">True</property>
+    <property name="default-width">600</property>
+    <property name="default-height">400</property>
+    <property name="modal">True</property>
+    <signal name="delete-event" handler="fingerprint_dialog_delete_cb"/>
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="titlebar">
+        <property name="visible">True</property>
+        <property name="show-close-button">True</property>
+        <child type="title">
+          <object class="GtkLabel" id="title">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">Fingerprint</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel_button">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="receives_default">False</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Cancel</property>
+            <signal name="clicked" handler="cancel_button_clicked_cb" object="CcFingerprintDialog" 
swapped="yes" />
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="receives_default">False</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <signal name="clicked" handler="back_button_clicked_cb" object="CcFingerprintDialog" 
swapped="yes" />
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child internal-child="accessible">
+              <object class="AtkObject" id="a11y-back">
+                <property name="accessible-name" translatable="yes">Back</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">go-previous-symbolic</property>
+                <property name="icon_size">1</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkButton" id="done_button">
+            <property name="use-underline">True</property>
+            <property name="can-default">True</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="label" translatable="yes">_Done</property>
+            <signal name="clicked" handler="done_button_clicked_cb" object="CcFingerprintDialog" 
swapped="yes" />
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkSpinner" id="spinner">
+            <property name="visible">True</property>
+            <property name="active">False</property>
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+
+        <child>
+          <object class="GtkInfoBar" id="delete_confirmation_infobar">
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">0</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">5</property>
+            <child internal-child="action_area">
+              <object class="GtkButtonBox">
+                <property name="can_focus">False</property>
+                <property name="layout_style">end</property>
+                <child>
+                  <object class="GtkButton">
+                    <signal name="clicked" handler="cancel_deletion_button_clicked_cb" 
object="CcFingerprintDialog" swapped="yes"/>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="label" translatable="yes">_No</property>
+                    <property name="use-underline">True</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <signal name="clicked" handler="confirm_deletion_button_clicked_cb" 
object="CcFingerprintDialog" swapped="yes"/>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="label" translatable="yes">_Yes</property>
+                    <property name="use-underline">True</property>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child internal-child="content_area">
+              <object class="GtkBox">
+                <property name="can_focus">False</property>
+                <property name="spacing">16</property>
+                <property name="margin-start">12</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="hexpand">False</property>
+                    <property name="wrap">True</property>
+                    <property name="label" translatable="yes">Do you want to delete your registered 
fingerprints so fingerprint login is disabled?</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkInfoBar" id="error_infobar">
+            <property name="name">error_infobar</property>
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">0</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <style>
+              <class name="error"/>
+            </style>
+            <child internal-child="content_area">
+              <object class="GtkBox">
+                <property name="can_focus">False</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="infobar_error">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="hexpand">False</property>
+                    <property name="wrap">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">True</property>
+            <property name="halign">fill</property>
+            <property name="valign">fill</property>
+            <property name="propagate-natural-width">True</property>
+            <property name="can-focus">False</property>
+            <property name="hscrollbar-policy">never</property>
+
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="visible">True</property>
+                <property name="transition_duration">300</property>
+                <property name="margin-start">20</property>
+                <property name="margin-end">20</property>
+                <property name="margin-top">30</property>
+                <property name="margin-bottom">30</property>
+                <property name="width_request">360</property>
+                <property name="halign">center</property>
+
+                <child>
+                  <object class="GtkBox" id="no_devices_found">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="valign">center</property>
+                    <property name="spacing">12</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="icon_name">fingerprint-detection-symbolic</property>
+                        <property name="pixel_size">192</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes" comments="Translators: This is the empty 
state page label which states that there are no devices ready.">No Fingerprint device</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                          <attribute name="scale" value="1.6"/>
+                        </attributes>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Ensure the device is properly 
connected.</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name" translatable="yes">No fingerprint device</property>
+                  </packing>
+                </child>
+
+                <child>
+                  <object class="GtkBox" id="device_selector">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="spacing">10</property>
+                    <property name="orientation">vertical</property>
+
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Choose the fingerprint device you want to 
configure</property>
+                        <property name="halign">start</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+
+                    <child>
+                      <object class="GtkScrolledWindow">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="hscrollbar-policy">never</property>
+                        <property name="propagate-natural-height">True</property>
+                        <child>
+                          <object class="GtkListBox" id="devices_list">
+                            <property name="visible">True</property>
+                            <property name="selection-mode">none</property>
+                            <property name="valign">center</property>
+                            <signal name="row-activated" handler="select_device_row" 
object="CcFingerprintDialog" swapped="yes"/>
+                            <style>
+                              <class name="frame" />
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name" translatable="yes">Fingerprint Device</property>
+                  </packing>
+                </child>
+
+                <child>
+                  <object class="GtkBox" id="prints_manager">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="valign">fill</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Fingerprint login allows you to unlock and 
log into your computer with your finger</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkFlowBox" id="prints_gallery">
+                        <style>
+                          <class name="prints-gallery" />
+                        </style>
+                        <property name="visible">True</property>
+                        <property name="margin">12</property>
+                        <property name="column-spacing">12</property>
+                        <property name="row-spacing">12</property>
+                        <property name="homogeneous">True</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <property name="vexpand">True</property>
+                        <property name="min-children-per-line">1</property>
+                        <property name="max-children-per-line">3</property>
+                        <property name="activate-on-single-click">True</property>
+                        <property name="selection-mode">none</property>
+                        <signal name="child-activated" handler="on_print_activated_cb" 
object="CcFingerprintDialog" swapped="no" />
+                      </object>
+                    </child>
+
+                    <child>
+                      <object class="GtkButton" id="delete_prints_button">
+                        <property name="visible">False</property>
+                        <property name="can_focus">True</property>
+                        <property name="halign">end</property>
+                        <property name="use-underline">True</property>
+                        <property name="label" translatable="yes">_Delete Fingerprints</property>
+                        <property name="margin-top">10</property>
+                        <property name="margin-bottom">10</property>
+                        <signal name="clicked" handler="delete_prints_button_clicked_cb" 
object="CcFingerprintDialog" swapped="yes"/>
+                        <style>
+                          <class name="destructive-action"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name" translatable="yes">Fingerprint Login</property>
+                  </packing>
+                </child>
+
+                <child>
+                  <object class="GtkBox" id="enrollment_view">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="valign">fill</property>
+                    <property name="spacing">12</property>
+                    <style>
+                      <class name="enrollment" />
+                    </style>
+                    <child>
+                      <object class="GtkLabel" id="enroll_message">
+                        <property name="visible">True</property>
+                        <property name="wrap">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="margin">12</property>
+                        <property name="spacing">12</property>
+                        <property name="halign">fill</property>
+                        <property name="valign">center</property>
+                        <property name="vexpand">True</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkBox" id="enroll_print_bin">
+                            <property name="halign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="enroll_print_entry">
+                            <property name="valign">end</property>
+                            <property name="visible">True</property>
+                            <property name="halign">center</property>
+                            <property name="editable">False</property>
+                            <property name="sensitive">False</property>
+                            <property name="width-request">200</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name" translatable="yes">Fingerprint Enroll</property>
+                  </packing>
+                </child>
+
+              </object>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+
+  <object class="GtkPopoverMenu" id="print_popover">
+    <property name="position">bottom</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin">12</property>
+        <property name="spacing">6</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkModelButton">
+            <property name="label" translatable="yes">_Re-enroll this finger…</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0.0</property>
+            <signal name="clicked" handler="reenroll_finger_cb" object="CcFingerprintDialog" swapped="yes"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+
+  <object class="GtkPopoverMenu" id="add_print_popover">
+    <property name="position">bottom</property>
+    <child>
+      <object class="GtkBox" id="add_print_popover_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin">12</property>
+        <property name="spacing">6</property>
+        <property name="orientation">vertical</property>
+      </object>
+    </child>
+  </object>
+
+</interface>
diff --git a/panels/user-accounts/cc-user-panel.c b/panels/user-accounts/cc-user-panel.c
index 81ad6825f..5a9b5c221 100644
--- a/panels/user-accounts/cc-user-panel.c
+++ b/panels/user-accounts/cc-user-panel.c
@@ -21,7 +21,6 @@
 #include "config.h"
 
 #include "cc-user-panel.h"
-#include "cc-fingerprint-manager.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -53,7 +52,8 @@
 #include "cc-realm-manager.h"
 #include "cc-user-accounts-resources.h"
 #include "cc-user-image.h"
-#include "um-fingerprint-dialog.h"
+#include "cc-fingerprint-manager.h"
+#include "cc-fingerprint-dialog.h"
 #include "user-utils.h"
 
 #include "cc-common-language.h"
@@ -113,7 +113,6 @@ struct _CcUserPanel {
 
         CcAvatarChooser *avatar_chooser;
 
-        GCancellable *fingerprint_cancellable;
         CcFingerprintManager *fingerprint_manager;
 
         gint other_accounts;
@@ -928,7 +927,6 @@ show_user (ActUser *user, CcUserPanel *self)
         if (show) {
                 if (!self->fingerprint_manager) {
                         self->fingerprint_manager = cc_fingerprint_manager_new (user);
-                        fingerprint_set_manager (self->fingerprint_manager);
                         g_signal_connect_object (self->fingerprint_manager,
                                                  "notify::state",
                                                  G_CALLBACK (update_fingerprint_row_state),
@@ -1181,19 +1179,17 @@ static void
 change_fingerprint (CcUserPanel *self)
 {
         ActUser *user;
+        GtkWindow *top_level;
+        CcFingerprintDialog *dialog;
 
         user = get_selected_user (self);
+        top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
 
         g_assert (g_strcmp0 (g_get_user_name (), act_user_get_user_name (user)) == 0);
 
-        g_cancellable_cancel (self->fingerprint_cancellable);
-        g_clear_object (&self->fingerprint_cancellable);
-
-        self->fingerprint_cancellable = g_cancellable_new ();
-
-        fingerprint_button_clicked (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
-                                    GTK_WIDGET (self->fingerprint_row),
-                                    self->fingerprint_cancellable);
+        dialog = cc_fingerprint_dialog_new (self->fingerprint_manager);
+        gtk_window_set_transient_for (GTK_WINDOW (dialog), top_level);
+        gtk_widget_show (GTK_WIDGET (dialog));
 }
 
 static void
@@ -1602,9 +1598,6 @@ cc_user_panel_dispose (GObject *object)
 
         g_clear_object (&self->login_screen_settings);
 
-        g_cancellable_cancel (self->fingerprint_cancellable);
-        g_clear_object (&self->fingerprint_cancellable);
-
         g_clear_pointer ((GtkWidget **)&self->language_chooser, gtk_widget_destroy);
         g_clear_object (&self->permission);
         G_OBJECT_CLASS (cc_user_panel_parent_class)->dispose (object);
diff --git a/panels/user-accounts/data/cc-fingerprint-dialog.css 
b/panels/user-accounts/data/cc-fingerprint-dialog.css
new file mode 100644
index 000000000..9f8ea1770
--- /dev/null
+++ b/panels/user-accounts/data/cc-fingerprint-dialog.css
@@ -0,0 +1,61 @@
+.fingerprint-icon {
+  padding: 3px;
+}
+
+.fingerprint-icon > button,
+.fingerprint-icon > image {
+  padding: 15px;
+  min-width: 32px;
+  min-height: 32px;
+  border-radius: 64px;
+  border: 1px solid @borders;
+  background-color: @theme_base_color;
+  color: @insensitive_fg_color;
+}
+
+.fingerprint-print-add image:not(:disabled):not(:backdrop),
+.fingerprint-print-add button:not(:disabled):not(:backdrop) {
+  color: @theme_fg_color;
+}
+
+.fingerprint-icon.enroll-status image {
+  outline-color: @theme_selected_bg_color;
+  outline-offset: 0px;
+  outline-width: 4px;
+}
+
+.fingerprint-icon.enroll-status image:backdrop {
+  outline-color: @theme_unfocused_selected_bg_color;
+}
+
+.fingerprint-icon.enroll-status {
+  font-weight: bold;
+}
+
+.fingerprint-icon.enroll-status.completed image {
+  outline-color: @success_color;
+}
+
+.fingerprint-icon.enroll-status.warning image {
+  outline-color: @warning_color;
+}
+
+.fingerprint-icon.enroll-status.error image {
+  outline-color: @error_color;
+  /* Given we don't have an error image, we can just recolorize the warning one */
+  -gtk-icon-palette: warning @error_color;
+}
+
+.fingerprint-icon.enroll-status.success image:not(:backdrop) {
+  color: @theme_selected_bg_color;
+}
+
+.fingerprint-icon.enroll-status.warning image:not(:backdrop),
+.fingerprint-icon.enroll-status.warning label:not(:backdrop) {
+  color: @warning_color;
+}
+
+.fingerprint-icon.enroll-status.error image:not(:backdrop),
+.fingerprint-icon.enroll-status.error label:not(:backdrop) {
+  color: @error_color;
+}
diff --git a/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg 
b/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg
new file mode 100644
index 000000000..a22538465
--- /dev/null
+++ b/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="32" height="32" viewBox="0 0 8.467 8.467">
+    <path 
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"
 d="M 16.132812,0.99023438 C 13.902514,0.96755721 11.66847,1.5168081 9.65625,2.6425781 8.4831917,3.288913 
9.4616217,5.0473362 10.628906,4.390625 14.036766,2.4839667 18.198267,2.527856 21.566406,4.5039062 
24.934547,6.4799188 27,10.08815 27,13.994141 v 2 c -5.97e-4,1.33435 1.999403,1.33435 2,0 v -2 c 
3.54e-4,-0.03325 -9.56e-4,-0.06649 -0.0039,-0.09961 C 28.961052,9.320245 26.52689,5.092052 
22.578125,2.7753906 20.589365,1.6086127 18.363092,1.0130627 16.132812,0.99023438 Z M 5.7617188,6.640625 C 
5.4310747,6.6594699 5.1312075,6.
 840853 4.9609375,7.125 3.6959444,9.158537 3.0222407,11.500805 3.0039062,13.894531 3.00095,13.927649 
2.9996462,13.960893 3,13.994141 v 10.128906 c 0,1.333754 2,1.333754 2,0 V 13.994141 C 5,11.939136 
5.5747962,9.9244686 6.6601562,8.1796875 7.1014645,7.4921558 6.5771855,6.5940433 5.7617188,6.640625 Z M 
16,6.9921875 c -3.813165,0 -6.9283277,3.0816025 -6.9941406,6.8808595 -0.0044,0.04021 -0.00636,0.08065 
-0.00586,0.121094 v 2 c 5.96e-4,1.333157 2.000596,1.333157 2,0 v -2 c 0,-2.774325 2.22666,-5.0019535 
5,-5.0019535 2.77334,0 5,2.2276285 5,5.0019535 V 22.125 c 0.426667,0.161975 0.81076,0.41915 1.123047,0.751953 
L 23,23.753906 v -9.759765 c 2.65e-4,-0.03849 -0.0017,-0.07697 -0.0059,-0.115235 C 22.931551,10.076928 
19.815145,6.9921875 16,6.9921875 Z m -0.01563,5.9863285 C 15.43218,12.98705 14.991449,13.441767 15,13.994141 
v 10.003906 c 0,0 -5.87e-4,1.09432 0.269531,2.445312 0.2701,1.350992 0.787778,3.027578 2.023438,4.263672 
0.942205,0.981983 2.395438,-0.47125 1.414062,-1.414062 -0.690333,-
 0.690533 -1.138586,-1.835412 -1.390625,-2.898438 l -0.002,-0.002 C 16.951575,25.742041 16.843618,24.979658 
17.011719,24.253906 17.009401,24.177001 17,23.998047 17,23.998047 V 13.994141 c 0.0087,-0.564623 
-0.451183,-1.024549 -1.015625,-1.015625 z m -6,8.001953 C 9.4321797,20.989003 8.9914495,21.44372 9,21.996094 
v 6.001953 c -5.966e-4,1.33435 1.999403,1.33435 2,0 v -6.001953 c 0.0087,-0.564623 -0.451183,-1.024549 
-1.015625,-1.015625 z m 20.998047,0.0059 a 1.0001,1.0001 0 0 0 -0.6875,0.302734 l -6.296875,6.289063 
-3.291016,-3.292969 a 1.0001,1.0001 0 1 0 -1.410156,1.417969 l 4.701172,4.703125 7.707031,-7.707031 a 
1.0001,1.0001 0 0 0 -0.722656,-1.712891 z" transform="scale(.26458)" class="success" color="#000" 
font-weight="400" font-family="sans-serif" overflow="visible" fill="#33d17a"/>
+</svg>
diff --git a/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg 
b/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg
new file mode 100644
index 000000000..00e31cce2
--- /dev/null
+++ b/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="32" height="32" viewBox="0 0 8.467 8.467">
+    <path d="m 4.2684727,288.79533 c -0.5901,-0.006 -1.1811905,0.13932 -1.7135904,0.43718 a 
0.26457931,0.26466281 0 1 0 0.2573486,0.46251 c 0.9016629,-0.50447 2.0027268,-0.49283 2.8938802,0.03 
0.8911538,0.52282 1.4376385,1.4775 1.4376385,2.51096 v 0.52916 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v 
-0.52916 a 0.26456806,0.26465154 0 0 0 -0.00103,-0.0264 c -0.00927,-1.21028 -0.6533118,-2.32899 
-1.6980893,-2.94194 -0.5261927,-0.30871 -1.1152275,-0.46628 -1.7053224,-0.47232 z m -2.7440185,1.495 a 
0.26456806,0.26465154 0 0 0 -0.2118734,0.12816 c -0.3346961,0.53804 -0.51294685,1.15776 -0.51779785,1.7911 a 
0.26456806,0.26465154 0 0 0 -0.001034,0.0264 v 2.67994 a 0.26458334,0.26466683 0 0 0 0.52916665,0 v -2.67994 
c 0,-0.54372 0.1520815,-1.07677 0.4392497,-1.53841 a 0.26456806,0.26465154 0 0 0 -0.2377116,-0.40721 z m 
2.7088787,0.093 c -1.0089,0 -1.8331201,0.81534 -1.8505331,1.82056 a 0.26456806,0.26465154 0 0 0 
-0.00155,0.032 v 0.52916 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -0.52916 c
  0,-0.73404 0.5891371,-1.32344 1.3229167,-1.32344 0.7337795,0 1.3229167,0.5894 1.3229167,1.32344 v 2.64686 c 
0,0 0.00212,0.18248 0.079582,0.41445 0.077301,0.23197 0.2313432,0.5351 0.5270998,0.83096 a 
0.26456806,0.26465154 0 1 0 0.3741372,-0.37414 c -0.2334101,-0.23348 -0.3439509,-0.45923 -0.3989419,-0.62425 
-0.054991,-0.16503 -0.05271,-0.24702 -0.05271,-0.24702 v -2.64686 a 0.26456806,0.26465154 0 0 0 
-0.00156,-0.0305 c -0.016571,-1.00594 -0.8410985,-1.82211 -1.8505223,-1.82211 z m -0.00414,1.58388 a 
0.26456806,0.26465154 0 0 0 -0.2604479,0.26872 v 2.64686 c 0,0 -1.558e-4,0.28954 0.071313,0.64699 
0.071464,0.35745 0.208433,0.80105 0.535368,1.1281 a 0.26456806,0.26465154 0 1 0 0.3741373,-0.37414 c 
-0.2022317,-0.20229 -0.329846,-0.55309 -0.3906737,-0.85731 -0.060823,-0.30422 -0.060978,-0.54364 
-0.060978,-0.54364 v -2.64686 a 0.26456806,0.26465154 0 0 0 -0.2687175,-0.26872 z m -1.5875,2.11718 a 
0.26456806,0.26465154 0 0 0 -0.2604479,0.26872 v 1.58802 a 0.26458335,0.26466684 0 1 0 0.5291
 667,0 v -1.58802 a 0.26456806,0.26465154 0 0 0 -0.2687175,-0.26872 z m 4.7625001,0 a 0.26456806,0.26465154 0 
0 0 -0.2604479,0.26769 v 0.52968 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -0.52968 a 0.26456806,0.26465154 
0 0 0 -0.2687175,-0.26769 z" 
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"
 color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#3d3846" 
transform="translate(0 -288.533)"/>
+</svg>
diff --git a/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg 
b/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg
new file mode 100644
index 000000000..5b3fa81fa
--- /dev/null
+++ b/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="32" height="32" viewBox="0 0 8.467 8.467">
+    <path overflow="visible" font-weight="400" d="m 4.2684194,0.26199621 c -0.5900922,-0.006 
-1.1811756,0.13932088 -1.7135688,0.4371771 A 0.26457597,0.26465948 0 1 0 2.8121959,1.1616716 C 
3.7138475,0.65720791 4.8148975,0.66882014 5.7060397,1.1916435 6.5971824,1.7144569 7.14366,2.6691227 
7.14366,3.7025698 v 0.52916 a 0.26458003,0.2646635 0 1 0 0.52916,0 v -0.52916 A 0.26456472,0.26464821 0 0 0 
7.6717881,3.676215 C 7.6625167,2.4659504 7.0184846,1.3472551 5.9737203,0.73431284 5.4475342,0.42560675 
4.8585069,0.26803613 4.2684194,0.26199621 Z M 1.5244356,1.7569766 A 0.26456472,0.26464821 0 0 0 
1.3125648,1.8851325 C 0.97787297,2.4231657 0.79962444,3.042883 0.7947735,3.676215 A 0.26456472,0.26464821 0 0 
0 0.79374,3.7025698 v 2.679906 a 0.26458,0.2646635 0 0 0 0.52916,0 v -2.679906 c 0,-0.5437132 
0.1520796,-1.0767539 0.4392441,-1.5383881 A 0.26456472,0.26464821 0 0 0 1.5244356,1.7569766 Z M 
4.23328,1.849993 c -1.0088872,0 -1.8330969,0.8153304 -1.8505097,1.8205378 a 0.26456472,0.26464821 0 0 
 0 -0.00155,0.032039 v 0.52916 a 0.26458003,0.2646635 0 0 0 0.52916,0 v -0.52916 c 0,-0.7340309 
0.5891297,-1.3234168 1.3229,-1.3234168 0.7337703,0 1.3229,0.5893859 1.3229,1.3234168 V 4.4110447 C 
5.6854821,4.2368203 5.8715938,4.0894366 6.08534,4.0260602 V 3.7025698 A 0.26456472,0.26464821 0 0 0 
6.083779,3.6720809 C 6.0672189,2.6661536 5.2426911,1.849993 4.23328,1.849993 Z M 4.22914,3.4338558 A 
0.26456472,0.26464821 0 0 0 3.9687,3.7025698 v 2.6468335 c 0,0 -1.553e-4,0.2895352 0.071312,0.6469806 
0.00877,0.043848 0.022106,0.091907 0.033073,0.1379745 L 4.49786,6.3468194 V 3.7025698 A 0.26456472,0.26464821 
0 0 0 4.2291459,3.4338558 Z M 6.3251097,4.5226644 C 6.1860729,4.5146644 6.0504148,4.5975684 
5.944776,4.7789762 L 4.2911556,7.8464506 C 4.1500114,8.1013375 4.3039147,8.46656 4.5805412,8.46656 h 
3.4808807 c 0.2597435,0 0.5035248,-0.3067533 0.3224568,-0.6201094 L 6.7137175,4.7955125 C 6.6082242,4.6245446 
6.4641524,4.5302343 6.3251156,4.5226644 Z M 2.6416659,5.5510125 A 0.26456472,0.26464821
  0 0 0 2.38122,5.8197266 v 1.5879967 a 0.26458003,0.2646635 0 1 0 0.52916,0 V 5.8197266 A 
0.26456472,0.26464821 0 0 0 2.6416659,5.5510125 Z m 3.699986,0.013436 C 6.4857104,5.5594481 
6.6190614,5.693238 6.6145,5.8372962 V 6.87908 C 6.61635,7.0188682 6.4897161,7.14366 6.34992,7.14366 
6.2101265,7.14366 6.0833636,7.0188682 6.08534,6.87908 V 5.8372962 c -0.00212,-0.1234583 0.093688,-0.2414303 
0.2149712,-0.26458 0.01352,-0.004 0.027371,-0.00675 0.041341,-0.00775 z M 6.34992,7.40824 c 0.1461236,0 
0.26458,0.1184617 0.26458,0.26458 0,0.1461281 -0.1184564,0.26458 -0.26458,0.26458 -0.1461236,0 
-0.26458,-0.1184519 -0.26458,-0.26458 0,-0.1461183 0.1184564,-0.26458 0.26458,-0.26458 z" 
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-tra
 
nsform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"
 class="warning" color="#000" font-family="sans-serif" fill="#ff7800"/>
+</svg>
diff --git a/panels/user-accounts/meson.build b/panels/user-accounts/meson.build
index de54ee14e..b8ee9d98e 100644
--- a/panels/user-accounts/meson.build
+++ b/panels/user-accounts/meson.build
@@ -107,22 +107,14 @@ resource_data = files(
   'cc-login-history-dialog.ui',
   'cc-password-dialog.ui',
   'cc-user-panel.ui',
-  'data/icons/left-index-finger.png',
-  'data/icons/left-little-finger.png',
-  'data/icons/left-middle-finger.png',
-  'data/icons/left-ring-finger.png',
-  'data/icons/left-thumb.png',
-  'data/icons/print_error.png',
-  'data/icons/print_ok.png',
-  'data/icons/right-index-finger.png',
-  'data/icons/right-little-finger.png',
-  'data/icons/right-middle-finger.png',
-  'data/icons/right-ring-finger.png',
-  'data/icons/right-thumb.png',
-  'data/account-fingerprint.ui',
+  'cc-fingerprint-dialog.ui',
+  'data/icons/fingerprint-detection-complete-symbolic.svg',
+  'data/icons/fingerprint-detection-symbolic.svg',
+  'data/icons/fingerprint-detection-warning-symbolic.svg',
   'data/carousel.css',
   'data/join-dialog.ui',
   'data/user-accounts-dialog.css',
+  'data/cc-fingerprint-dialog.css',
 )
 
 common_sources += gnome.compile_resources(
@@ -165,12 +157,12 @@ sources = common_sources + files(
   'cc-carousel.c',
   'cc-crop-area.c',
   'cc-fingerprint-manager.c',
+  'cc-fingerprint-dialog.c',
   'cc-login-history-dialog.c',
   'cc-password-dialog.c',
   'cc-user-image.c',
   'cc-user-panel.c',
   'run-passwd.c',
-  'um-fingerprint-dialog.c',
 )
 
 sources += gnome.mkenums_simple(
diff --git a/panels/user-accounts/user-accounts.gresource.xml 
b/panels/user-accounts/user-accounts.gresource.xml
index 351080783..fcd1a7fbc 100644
--- a/panels/user-accounts/user-accounts.gresource.xml
+++ b/panels/user-accounts/user-accounts.gresource.xml
@@ -7,21 +7,16 @@
     <file preprocess="xml-stripblanks">cc-login-history-dialog.ui</file>
     <file preprocess="xml-stripblanks">cc-password-dialog.ui</file>
     <file preprocess="xml-stripblanks">cc-user-panel.ui</file>
+    <file preprocess="xml-stripblanks">cc-fingerprint-dialog.ui</file>
     <file alias="join-dialog.ui" preprocess="xml-stripblanks">data/join-dialog.ui</file>
-    <file alias="account-fingerprint.ui" preprocess="xml-stripblanks">data/account-fingerprint.ui</file>
     <file alias="user-accounts-dialog.css">data/user-accounts-dialog.css</file>
     <file alias="carousel.css">data/carousel.css</file>
-    <file alias="left-index-finger.png">data/icons/left-index-finger.png</file>
-    <file alias="left-middle-finger.png">data/icons/left-middle-finger.png</file>
-    <file alias="left-little-finger.png">data/icons/left-little-finger.png</file>
-    <file alias="left-ring-finger.png">data/icons/left-ring-finger.png</file>
-    <file alias="left-thumb.png">data/icons/left-thumb.png</file>
-    <file alias="print_error.png">data/icons/print_error.png</file>
-    <file alias="print_ok.png">data/icons/print_ok.png</file>
-    <file alias="right-index-finger.png">data/icons/right-index-finger.png</file>
-    <file alias="right-middle-finger.png">data/icons/right-middle-finger.png</file>
-    <file alias="right-little-finger.png">data/icons/right-little-finger.png</file>
-    <file alias="right-ring-finger.png">data/icons/right-ring-finger.png</file>
-    <file alias="right-thumb.png">data/icons/right-thumb.png</file>
+    <file alias="cc-fingerprint-dialog.css">data/cc-fingerprint-dialog.css</file>
+  </gresource>
+
+  <gresource prefix="/org/gnome/ControlCenter/icons/scalable/status">
+    <file preprocess="xml-stripblanks" 
alias="fingerprint-detection-complete-symbolic.svg">data/icons/fingerprint-detection-complete-symbolic.svg</file>
+    <file preprocess="xml-stripblanks" 
alias="fingerprint-detection-symbolic.svg">data/icons/fingerprint-detection-symbolic.svg</file>
+    <file preprocess="xml-stripblanks" 
alias="fingerprint-detection-warning-symbolic.svg">data/icons/fingerprint-detection-warning-symbolic.svg</file>
   </gresource>
 </gresources>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index fdebe09fa..0f809aaf0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -222,6 +222,8 @@ panels/user-accounts/cc-add-user-dialog.c
 panels/user-accounts/cc-add-user-dialog.ui
 panels/user-accounts/cc-avatar-chooser.c
 panels/user-accounts/cc-avatar-chooser.ui
+panels/user-accounts/cc-fingerprint-dialog.ui
+panels/user-accounts/cc-fingerprint-dialog.c
 panels/user-accounts/cc-login-history-dialog.c
 panels/user-accounts/cc-login-history-dialog.ui
 panels/user-accounts/cc-password-dialog.c
@@ -229,13 +231,11 @@ panels/user-accounts/cc-password-dialog.ui
 panels/user-accounts/cc-realm-manager.c
 panels/user-accounts/cc-user-panel.c
 panels/user-accounts/cc-user-panel.ui
-panels/user-accounts/data/account-fingerprint.ui
 panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
 panels/user-accounts/data/join-dialog.ui
 panels/user-accounts/org.gnome.controlcenter.user-accounts.policy.in
 panels/user-accounts/pw-utils.c
 panels/user-accounts/run-passwd.c
-panels/user-accounts/um-fingerprint-dialog.c
 panels/user-accounts/user-utils.c
 panels/wacom/button-mapping.ui
 panels/wacom/calibrator/calibrator.ui



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