Implement Multi-Device

This patch implements multi-device support:

- The account creation wizzard has now two options "Existing Ring
  account" and "New Ring account".

  "Existing Ring account": Allows for fetching a Ring account archive
  from the DHT. Requires pin and password.

  "New Ring account": This is the previously existing wizard. It was
  modified to ask for a password which will be used to encrypt the
  account archive. This password is then required for exporting the
  account on the Ring.

- Creating a new Ring account with the "+" button now triggers the
  account creation wizard.

- The account menu now has a "devices" tab. This tab contains a table
  with device names (currently a short hash) and device ids (a long
  hash).

  In the "devices" tab, there is an "add device" button which allows
  for exporting the current account to the Ring, giving a pin to the
  user.

- When the client encounters old-format accounts, it will trigger a
  migration popup which asks the user for a password. This password will
  be used to create an archive and encrypt it. One password will be
  asked for each Ring account to migrate.

Change-Id: I3d52b2b7ca4f82cb477ee294c962b5d50d5c6a04
Tuleap: #896
diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp
index 95d86d3..15cd5a3 100644
--- a/src/ringmainwindow.cpp
+++ b/src/ringmainwindow.cpp
@@ -64,13 +64,16 @@
 #include "generalsettingsview.h"
 #include "utils/accounts.h"
 #include "ringwelcomeview.h"
+#include "accountmigrationview.h"
+#include "accountcreationwizard.h"
 #include "recentcontactsview.h"
 #include "chatview.h"
 #include "avatarmanipulation.h"
 #include "utils/files.h"
 
 static constexpr const char* CALL_VIEW_NAME             = "calls";
-static constexpr const char* CREATE_ACCOUNT_VIEW_NAME   = "wizard";
+static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
+static constexpr const char* ACCOUNT_MIGRATION_VIEW_NAME       = "account-migration-view";
 static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
 static constexpr const char* AUDIO_SETTINGS_VIEW_NAME   = "audio";
 static constexpr const char* MEDIA_SETTINGS_VIEW_NAME   = "media";
@@ -125,21 +128,6 @@
 
     gboolean   show_settings;
 
-    /* account creation */
-    GtkWidget *account_creation;
-    GtkWidget *image_ring_logo;
-    GtkWidget *vbox_account_creation_entry;
-    GtkWidget *entry_alias;
-    GtkWidget *label_default_name;
-    GtkWidget *label_paceholder;
-    GtkWidget *label_generating_account;
-    GtkWidget *spinner_generating_account;
-    GtkWidget *button_account_creation_next;
-    GtkWidget *box_avatarselection;
-    GtkWidget* avatar_manipulation;
-
-    QMetaObject::Connection hash_updated;
-
     /* allocd qmodels */
     NumberCompletionModel *q_completion_model;
 
@@ -591,153 +579,56 @@
     }
 }
 
-static gboolean
-create_ring_account(RingMainWindow *win)
-{
-    g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
-
-    /* create account and set UPnP enabled, as its not by default in the daemon */
-    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
-    Account *account = nullptr;
-
-    /* get profile (if so) */
-    auto profile = ProfileModel::instance().selectedProfile();
-
-    if (profile) {
-        if (alias && strlen(alias) > 0) {
-            account = AccountModel::instance().add(alias, Account::Protocol::RING);
-            profile->person()->setFormattedName(alias);
-        } else {
-            auto unknown_alias = C_("The default username / account alias, if none is set by the user", "Unknown");
-            account = AccountModel::instance().add(unknown_alias, Account::Protocol::RING);
-            profile->person()->setFormattedName(unknown_alias);
-        }
-    }
-
-    account->setDisplayName(alias); // set the display name to the same as the alias
-    account->setUpnpEnabled(TRUE);
-
-    /* wait for hash to be generated to show the main view */
-    priv->hash_updated = QObject::connect(
-        account,
-        &Account::changed,
-        [=] (Account *a) {
-            QString hash = a->username();
-            if (!hash.isEmpty()) {
-                /* show the call view */
-                gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
-                gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
-
-                /* show the settings button*/
-                gtk_widget_show(priv->ring_settings);
-
-                //once the account is created we don't want this lambda to be called again
-                QObject::disconnect(priv->hash_updated);
-            }
-        }
-    );
-
-    account->performAction(Account::EditAction::SAVE);
-    profile->save();
-
-    return G_SOURCE_REMOVE;
-}
-
 static void
-alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
+on_account_creation_completed(RingMainWindow *win)
 {
     g_return_if_fail(IS_RING_MAIN_WINDOW(win));
     RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
 
-    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
-    if (!alias || strlen(alias) == 0) {
-        gtk_widget_show(priv->label_default_name);
-        gtk_widget_hide(priv->label_paceholder);
-    } else {
-        gtk_widget_hide(priv->label_default_name);
-        gtk_widget_show(priv->label_paceholder);
+    gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
+    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
+
+    /* destroy the wizard */
+    GtkWidget* account_creation_wizard = gtk_stack_get_child_by_name(
+        GTK_STACK(priv->stack_main_view),
+        ACCOUNT_CREATION_WIZARD_VIEW_NAME
+    );
+    if (account_creation_wizard)
+    {
+        gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), account_creation_wizard);
+        gtk_widget_destroy(account_creation_wizard);
     }
+
+    /* show the settings button*/
+    gtk_widget_show(priv->ring_settings);
 }
 
 static void
-account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
+show_account_creation_wizard(RingMainWindow *win)
 {
     RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
 
-    /* show/hide relevant widgets */
-    gtk_widget_hide(priv->vbox_account_creation_entry);
-    gtk_widget_hide(priv->button_account_creation_next);
-    gtk_widget_show(priv->label_generating_account);
-    gtk_widget_show(priv->spinner_generating_account);
+    auto account_creation_wizard = gtk_stack_get_child_by_name(
+        GTK_STACK(priv->stack_main_view),
+        ACCOUNT_CREATION_WIZARD_VIEW_NAME
+    );
 
-    /* make sure the AvatarManipulation widget is destroyed so the VideoWidget inside of it is too;
-     * NOTE: destorying its parent (box_avatarselection) first will cause a mystery 'BadDrawable'
-     * crash due to X error */
-    gtk_container_remove(GTK_CONTAINER(priv->box_avatarselection), priv->avatar_manipulation);
-    priv->avatar_manipulation = nullptr;
+    if (!account_creation_wizard)
+    {
+        account_creation_wizard = account_creation_wizard_new(false);
+        g_signal_connect_swapped(account_creation_wizard, "account-creation-completed", G_CALLBACK(on_account_creation_completed), win);
 
-    /* now create account after a short timeout so that the the save doesn't
-     * happen freeze the client before the widget changes happen;
-     * the timeout function should then display the next step in account creation
-     */
-    g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
-}
-
-static void
-entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
-{
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
-
-    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
-    if (strlen(alias) > 0)
-        gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
-}
-
-static void
-show_account_creation(RingMainWindow *win)
-{
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
-
-    gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
-                        priv->account_creation,
-                        CREATE_ACCOUNT_VIEW_NAME);
+        gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
+                            account_creation_wizard,
+                            ACCOUNT_CREATION_WIZARD_VIEW_NAME);
+    }
 
     /* hide settings button until account creation is complete */
     gtk_widget_hide(priv->ring_settings);
 
-    /* set ring logo */
-    GError *error = NULL;
-    GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
-                                                                  -1, 50, TRUE, &error);
-    if (logo_ring == NULL) {
-        g_debug("Could not load logo: %s", error->message);
-        g_clear_error(&error);
-    } else
-        gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
+    gtk_widget_show(account_creation_wizard);
 
-    /* use the real name / username of the logged in user as the default */
-    const char* real_name = g_get_real_name();
-    const char* user_name = g_get_user_name();
-    g_debug("real_name = %s",real_name);
-    g_debug("user_name = %s",user_name);
-
-    /* check first if the real name was determined */
-    if (g_strcmp0 (real_name,"Unknown") != 0)
-        gtk_entry_set_text(GTK_ENTRY(priv->entry_alias), real_name);
-    else
-        gtk_entry_set_text(GTK_ENTRY(priv->entry_alias), user_name);
-
-    /* avatar manipulation widget */
-    priv->avatar_manipulation = avatar_manipulation_new_from_wizard();
-    gtk_box_pack_start(GTK_BOX(priv->box_avatarselection), priv->avatar_manipulation, true, true, 0);
-
-    /* connect signals */
-    g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
-    g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
-    g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
-
-    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_VIEW_NAME);
+    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_CREATION_WIZARD_VIEW_NAME);
 }
 
 static void
@@ -1121,6 +1012,46 @@
 }
 
 static void
+handle_account_migrations(RingMainWindow *win)
+{
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
+
+    /* If there is an existing migration view, remove it */
+    GtkWidget* old_view = gtk_stack_get_child_by_name(GTK_STACK(priv->stack_main_view), ACCOUNT_MIGRATION_VIEW_NAME);
+    if (old_view)
+    {
+        gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), old_view);
+    }
+
+    QList<Account*> accounts = AccountModel::instance().accountsToMigrate();
+    if (!accounts.isEmpty())
+    {
+        Account* account = accounts.first();
+        g_debug("Migrating account: %s", account->id().toStdString().c_str());
+
+        GtkWidget* account_migration_view = account_migration_view_new(account);
+        g_signal_connect_swapped(account_migration_view, "account-migration-completed", G_CALLBACK(handle_account_migrations), win);
+        g_signal_connect_swapped(account_migration_view, "account-migration-failed", G_CALLBACK(handle_account_migrations), win);
+
+        gtk_widget_hide(priv->ring_settings);
+        gtk_widget_show(account_migration_view);
+        gtk_stack_add_named(
+            GTK_STACK(priv->stack_main_view),
+            account_migration_view,
+            ACCOUNT_MIGRATION_VIEW_NAME
+        );
+        gtk_stack_set_visible_child_name(
+            GTK_STACK(priv->stack_main_view),
+            ACCOUNT_MIGRATION_VIEW_NAME
+        );
+    }
+    else
+    {
+        gtk_widget_show(priv->ring_settings);
+    }
+}
+
+static void
 ring_main_window_init(RingMainWindow *win)
 {
     RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
@@ -1177,7 +1108,7 @@
         gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
     } else {
         /* user has to create the ring account */
-        show_account_creation(win);
+        show_account_creation_wizard(win);
     }
 
     /* init the settings views */
@@ -1322,6 +1253,8 @@
     /* set the search entry placeholder text */
     gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
                                    C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
+
+    handle_account_migrations(win);
 }
 
 static void
@@ -1374,18 +1307,6 @@
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
-
-    /* account creation */
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_account_creation_entry);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_default_name);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_paceholder);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, box_avatarselection);
 }
 
 GtkWidget *