[gnome-contacts] Use individual directly



commit cfbb16af60b8bfcb480b841ea6e4d09aebaa38cb
Author: Julian Sparber <julian sparber net>
Date:   Mon Jul 15 16:31:03 2019 +0200

    Use individual directly
    
    This removes most of the Contact class and makes use of the Individual
    of folks directly.
    The aim of this refactor is to make the code more readable.
    Since Individual provides most of the needed features the Contact class
    adds a complicated abstraction without any real benefit.
    
    Transform FakePersona a real Persona which can be used as a placeholder
    till folks returns the real Persona.
    
    Use SearchQuery to find the best matching contact when opening
    gnome-contacts with a e-mail address

 src/contacts-accounts-list.vala          |   2 +-
 src/contacts-app.vala                    |  23 +-
 src/contacts-avatar-selector.vala        |  10 +-
 src/contacts-avatar.vala                 |  32 +-
 src/contacts-contact-editor.vala         |  43 +--
 src/contacts-contact-form.vala           |   4 +-
 src/contacts-contact-list.vala           |  96 ++++--
 src/contacts-contact-pane.vala           |  82 +++--
 src/contacts-contact-sheet.vala          |  48 ++-
 src/contacts-contact.vala                | 573 -------------------------------
 src/contacts-fake-persona-store.vala     | 235 +++++++++----
 src/contacts-link-suggestion-grid.vala   |  24 +-
 src/contacts-linked-personas-dialog.vala |  16 +-
 src/contacts-linking.vala                |  39 +--
 src/contacts-list-pane.vala              |  27 +-
 src/contacts-store.vala                  | 108 +++---
 src/contacts-utils.vala                  | 396 +++++++++++++++++++++
 src/contacts-window.vala                 |  66 ++--
 src/meson.build                          |   1 -
 19 files changed, 885 insertions(+), 940 deletions(-)
---
diff --git a/src/contacts-accounts-list.vala b/src/contacts-accounts-list.vala
index 6f67397..1575555 100644
--- a/src/contacts-accounts-list.vala
+++ b/src/contacts-accounts-list.vala
@@ -88,7 +88,7 @@ public class Contacts.AccountsList : Box {
       }
       var source = (persona_store as Edsf.PersonaStore).source;
       var parent_source = eds_source_registry.ref_source (source.parent);
-      var provider_name = Contact.format_persona_store_name (persona_store);
+      var provider_name = Contacts.Utils.format_persona_store_name (persona_store);
 
       debug ("Contact store \"%s\"", provider_name);
 
diff --git a/src/contacts-app.vala b/src/contacts-app.vala
index 3ad4630..056350b 100644
--- a/src/contacts-app.vala
+++ b/src/contacts-app.vala
@@ -88,14 +88,17 @@ public class Contacts.App : Gtk.Application {
     return -1;
   }
 
-  public void show_contact (Contact? contact) {
-    window.set_shown_contact (contact);
+  public void show_contact (Individual? individual) {
+    window.set_shown_contact (individual);
   }
 
   public async void show_individual (string id) {
-    var contact = yield contacts_store.find_contact ( (c) => {
-        return c.individual.id == id;
-      });
+    Individual? contact = null;
+    try {
+      contact = yield contacts_store.aggregator.look_up_individual (id);
+    } catch (Error e) {
+      debug ("Couldn't look up individual");
+    }
     if (contact != null) {
       show_contact (contact);
     } else {
@@ -153,7 +156,6 @@ public class Contacts.App : Gtk.Application {
            var settings = new GLib.Settings ("org.freedesktop.folks");
            settings.set_string ("primary-store",
                                 "eds:%s".printf(e_store.id));
-           contacts_store.refresh ();
          }
        }
        contacts_store.disconnect (stores_changed_id);
@@ -221,11 +223,10 @@ public class Contacts.App : Gtk.Application {
   }
 
   public async void show_by_email (string email_address) {
-    var contact = yield contacts_store.find_contact ( (c) => {
-        return c.has_email (email_address);
-      });
-    if (contact != null) {
-      show_contact (contact);
+    var query = new SimpleQuery(email_address, { "email-addresses" });
+    Individual individual = yield contacts_store.find_contact (query);
+    if (individual != null) {
+      show_contact (individual);
     } else {
       var dialog = new MessageDialog (this.window, DialogFlags.DESTROY_WITH_PARENT, MessageType.ERROR, 
ButtonsType.CLOSE,
                                       _("No contact with email address %s found"), email_address);
diff --git a/src/contacts-avatar-selector.vala b/src/contacts-avatar-selector.vala
index 9181145..126d06d 100644
--- a/src/contacts-avatar-selector.vala
+++ b/src/contacts-avatar-selector.vala
@@ -34,7 +34,7 @@ public class Contacts.AvatarSelector : Popover {
 
   // This will provide the default thumbnails
   private Gnome.DesktopThumbnailFactory thumbnail_factory;
-  private Contact contact;
+  private Individual individual;
 
   [GtkChild]
   private FlowBox personas_thumbnail_grid;
@@ -53,10 +53,10 @@ public class Contacts.AvatarSelector : Popover {
    */
   public signal void set_avatar (GLib.Icon avatar_icon);
 
-  public AvatarSelector (Gtk.Widget relative, Contact? contact) {
+  public AvatarSelector (Gtk.Widget relative, Individual? individual) {
     this.set_relative_to(relative);
     this.thumbnail_factory = new Gnome.DesktopThumbnailFactory (Gnome.ThumbnailSize.NORMAL);
-    this.contact = contact;
+    this.individual = individual;
 
     update_thumbnail_grids ();
 
@@ -158,8 +158,8 @@ public class Contacts.AvatarSelector : Popover {
   }
 
   private void update_thumbnail_grids () {
-    if (this.contact != null) {
-      foreach (var p in contact.individual.personas) {
+    if (this.individual != null) {
+      foreach (var p in individual.personas) {
         var button = thumbnail_for_persona (p);
         if (button != null)
           this.personas_thumbnail_grid.add (button);
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index d5d0c25..9f54217 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -28,24 +28,24 @@ public class Contacts.Avatar : DrawingArea {
   private Gdk.Pixbuf? pixbuf = null;
   private Gdk.Pixbuf? cache = null;
 
-  private Contact? contact = null;
+  private Individual? individual = null;
   // We want to lazily load the Pixbuf to make sure we don't draw all contact avatars at once.
   // As long as there is no need for it to be drawn, keep this to false.
   private bool avatar_loaded = false;
 
-  public Avatar (int size, Contact? contact = null) {
-    this.contact = contact;
-    if (contact != null) {
-      contact.individual.notify["avatar"].connect ( (s, p) => {
-          load_avatar.begin ();
-        });
+  public Avatar (int size, Individual? individual = null) {
+    this.individual = individual;
+    if (individual != null) {
+      individual.notify["avatar"].connect ( (s, p) => {
+        load_avatar.begin ();
+      });
     }
 
     this.size = size;
     set_size_request (size, size);
 
     // If we don't have an avatar, don't try to load it later
-    this.avatar_loaded = (contact == null || contact.individual.avatar == null);
+    this.avatar_loaded = (individual == null || individual.avatar == null);
 
     show ();
   }
@@ -60,16 +60,16 @@ public class Contacts.Avatar : DrawingArea {
   }
 
   private async void load_avatar () {
-    assert (this.contact != null);
+    assert (this.individual != null);
 
     this.avatar_loaded = true;
     try {
-      var stream = yield this.contact.individual.avatar.load_async (this.size);
+      var stream = yield this.individual.avatar.load_async (this.size);
       this.cache = null;
       this.pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (stream, this.size, this.size, true);
       queue_draw ();
     } catch (Error e) {
-      debug ("Couldn't load avatar of contact %s. Reason: %s", this.contact.individual.display_name, 
e.message);
+      debug ("Couldn't load avatar of contact %s. Reason: %s", this.individual.display_name, e.message);
     }
   }
 
@@ -102,13 +102,13 @@ public class Contacts.Avatar : DrawingArea {
   private Gdk.Pixbuf create_fallback () {
     string name = "";
     bool show_label = false;
-    if (this.contact != null && this.contact.individual != null) {
+    if (this.individual != null) {
       name = find_display_name ();
       /* If we don't have a usable name use the display_name
        * to generate the color but don't show any label
        */
       if (name == "") {
-        name = this.contact.individual.display_name;
+        name = this.individual.display_name;
       } else {
         show_label = true;
       }
@@ -126,7 +126,7 @@ public class Contacts.Avatar : DrawingArea {
   private string find_display_name () {
     string name = "";
     Persona primary_persona = null;
-    foreach (var p in this.contact.individual.personas) {
+    foreach (var p in this.individual.personas) {
       if (p.store.is_primary_store) {
         primary_persona = p;
         break;
@@ -134,12 +134,12 @@ public class Contacts.Avatar : DrawingArea {
     }
     name = look_up_alias_for_display_name (primary_persona);
     if (name == "") {
-      foreach (var p in this.contact.individual.personas) {
+      foreach (var p in this.individual.personas) {
         name = look_up_alias_for_display_name (p);
       }
     }
     if (name == "") {
-      foreach (var p in this.contact.individual.personas) {
+      foreach (var p in this.individual.personas) {
         name = look_up_name_details_for_display_name (p);
       }
     }
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 3398bf0..74743c7 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -120,15 +120,15 @@ public class Contacts.ContactEditor : ContactForm {
     this.container_grid.size_allocate.connect(on_container_grid_size_allocate);
   }
 
-  public ContactEditor (Contact? contact, Store store, GLib.ActionGroup editor_actions) {
+  public ContactEditor (Individual? individual, Store store, GLib.ActionGroup editor_actions) {
     this.store = store;
-    this.contact = contact;
+    this.individual = individual;
 
     this.add_detail_button.get_popover ().insert_action_group ("edit", editor_actions);
 
-    if (contact != null) {
-      this.remove_button.sensitive = contact.can_remove_personas ();
-      this.linked_button.sensitive = contact.individual.personas.size > 1;
+    if (individual != null) {
+      this.remove_button.sensitive = Contacts.Utils.can_remove_personas (individual);
+      this.linked_button.sensitive = individual.personas.size > 1;
     } else {
       this.remove_button.hide ();
       this.linked_button.hide ();
@@ -137,7 +137,7 @@ public class Contacts.ContactEditor : ContactForm {
     create_avatar_button ();
     create_name_entry ();
 
-    if (contact != null)
+    if (individual != null)
       fill_in_contact ();
     else
       fill_in_empty ();
@@ -150,7 +150,7 @@ public class Contacts.ContactEditor : ContactForm {
     int last_store_position = 0;
     bool is_first_persona = true;
 
-    var personas = this.contact.get_personas_for_display ();
+    var personas = Contacts.Utils.get_personas_for_display (individual);
     foreach (var p in personas) {
       if (!is_first_persona) {
         this.container_grid.attach (create_persona_store_label (p), 0, i, 2);
@@ -587,7 +587,7 @@ public class Contacts.ContactEditor : ContactForm {
       } else {
        var details = p as EmailDetails;
        if (details != null) {
-         var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
+         var emails = Contacts.Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
          foreach (var email in emails) {
            attach_row_with_entry (row, TypeSet.email, email, email.value);
            rows.set (row, { email });
@@ -615,7 +615,7 @@ public class Contacts.ContactEditor : ContactForm {
       } else {
        var details = p as PhoneDetails;
        if (details != null) {
-         var phones = Contact.sort_fields<PhoneFieldDetails>(details.phone_numbers);
+         var phones = Contacts.Utils.sort_fields<PhoneFieldDetails>(details.phone_numbers);
          foreach (var phone in phones) {
            attach_row_with_entry (row, TypeSet.phone, phone, phone.value, type);
            rows.set (row, { phone });
@@ -836,8 +836,8 @@ public class Contacts.ContactEditor : ContactForm {
        if (field_entry.value.changed && !props_set.has_key (field_entry.key)) {
          PropertyData p = PropertyData ();
          p.persona = null;
-         if (contact != null) {
-           p.persona = contact.find_persona_from_uid (entry.key);
+         if (individual != null) {
+           p.persona = Contacts.Utils.find_persona_from_uid (individual, entry.key);
          }
 
          switch (field_entry.key) {
@@ -872,18 +872,7 @@ public class Contacts.ContactEditor : ContactForm {
     return props_set;
   }
 
-  public void add_new_row_for_property (Persona? p, string prop_name, string? type = null) {
-    /* Somehow, I need to ensure that p is the main/default/first persona */
-    Persona persona = null;
-    if (contact != null) {
-      if (p == null) {
-        persona = new FakePersona (this.store, contact);
-        writable_personas[persona.uid] = new HashMap<string, Field?> ();
-      } else {
-        persona = p;
-      }
-    }
-
+  public void add_new_row_for_property (Persona? persona, string prop_name, string? type = null) {
     int next_idx = 0;
     foreach (var fields in writable_personas.values) {
       if (fields.has_key (prop_name)) {
@@ -903,7 +892,7 @@ public class Contacts.ContactEditor : ContactForm {
 
   // Creates the contact's current avatar in a big button on top of the Editor
   private void create_avatar_button () {
-    this.avatar = new Avatar (PROFILE_SIZE, this.contact);
+    this.avatar = new Avatar (PROFILE_SIZE, this.individual);
 
     var button = new Button ();
     button.get_accessible ().set_name (_("Change avatar"));
@@ -915,7 +904,7 @@ public class Contacts.ContactEditor : ContactForm {
 
   // Show the avatar popover when the avatar is clicked
   private void on_avatar_button_clicked (Button avatar_button) {
-    var popover = new AvatarSelector (avatar_button, this.contact);
+    var popover = new AvatarSelector (avatar_button, this.individual);
     popover.set_avatar.connect ( (icon) =>  {
         this.avatar.set_data ("value", icon);
         this.avatar.set_data ("changed", true);
@@ -951,8 +940,8 @@ public class Contacts.ContactEditor : ContactForm {
     this.name_entry.placeholder_text = _("Add name");
     this.name_entry.set_data ("changed", false);
 
-    if (this.contact != null)
-        this.name_entry.text = this.contact.individual.display_name;
+    if (this.individual != null)
+        this.name_entry.text = this.individual.display_name;
 
     /* structured name change */
     this.name_entry.changed.connect (() => {
diff --git a/src/contacts-contact-form.vala b/src/contacts-contact-form.vala
index f44a2fb..198fa6a 100644
--- a/src/contacts-contact-form.vala
+++ b/src/contacts-contact-form.vala
@@ -40,7 +40,7 @@ public abstract class Contacts.ContactForm : Grid {
     "notes"
   };
 
-  protected Contact? contact;
+  protected Individual? individual;
 
   protected Store store;
 
@@ -80,7 +80,7 @@ public abstract class Contacts.ContactForm : Grid {
   protected Label create_persona_store_label (Persona p) {
     var store_name = new Label("");
     store_name.set_markup (Markup.printf_escaped ("<span font='16px bold'>%s</span>",
-                           Contact.format_persona_store_name_for_contact (p)));
+                           Contacts.Utils.format_persona_store_name_for_contact (p)));
     store_name.set_halign (Align.START);
     store_name.xalign = 0.0f;
     store_name.margin_start = 6;
diff --git a/src/contacts-contact-list.vala b/src/contacts-contact-list.vala
index 31b2603..5b7385e 100644
--- a/src/contacts-contact-list.vala
+++ b/src/contacts-contact-list.vala
@@ -28,17 +28,16 @@ public class Contacts.ContactList : ListBox {
   private class ContactDataRow : ListBoxRow {
     private const int LIST_AVATAR_SIZE = 48;
 
-    public Contact contact;
+    public Individual individual;
     private Label label;
     private Avatar avatar;
     public CheckButton selector_button;
     // Whether the selector should always be visible (or only on hover)
     private bool checkbox_exposed = false;
 
-    public ContactDataRow(Contact c) {
-      this.contact = c;
-      this.contact.individual.notify.connect (on_contact_changed);
-      this.contact.notify["hidden"].connect ((o, p) => changed());
+    public ContactDataRow(Individual i) {
+      this.individual = i;
+      this.individual.notify.connect (on_contact_changed);
 
       get_style_context (). add_class ("contact-data-row");
 
@@ -46,9 +45,9 @@ public class Contacts.ContactList : ListBox {
       grid.margin = 3;
       grid.margin_start = 9;
       grid.set_column_spacing (10);
-      this.avatar = new Avatar (LIST_AVATAR_SIZE, this.contact);
+      this.avatar = new Avatar (LIST_AVATAR_SIZE, this.individual);
 
-      this.label = new Label (c.individual.display_name);
+      this.label = new Label (individual.display_name);
       this.label.ellipsize = Pango.EllipsizeMode.END;
       this.label.valign = Align.CENTER;
       this.label.halign = Align.START;
@@ -71,7 +70,7 @@ public class Contacts.ContactList : ListBox {
     }
 
     private void on_contact_changed (Object obj, ParamSpec pspec) {
-      this.label.set_text (this.contact.individual.display_name);
+      this.label.set_text (this.individual.display_name);
       changed ();
     }
 
@@ -94,7 +93,7 @@ public class Contacts.ContactList : ListBox {
     }
   }
 
-  public signal void selection_changed (Contact? contact);
+  public signal void selection_changed (Individual? individual);
   public signal void contacts_marked (int contacts_marked);
 
   int nr_contacts_marked = 0;
@@ -124,8 +123,8 @@ public class Contacts.ContactList : ListBox {
 
     this.store.added.connect (contact_added_cb);
     this.store.removed.connect (contact_removed_cb);
-    foreach (var c in this.store.get_contacts ())
-      contact_added_cb (this.store, c);
+    foreach (var i in this.store.get_contacts ())
+      contact_added_cb (this.store, i);
 
     get_style_context ().add_class ("contacts-contact-list");
 
@@ -148,8 +147,8 @@ public class Contacts.ContactList : ListBox {
   }
 
   private int compare_rows (ListBoxRow row_a, ListBoxRow row_b) {
-    var a = ((ContactDataRow) row_a).contact.individual;
-    var b = ((ContactDataRow) row_b).contact.individual;
+    var a = ((ContactDataRow) row_a).individual;
+    var b = ((ContactDataRow) row_b).individual;
 
     // Always prefer favourites over non-favourites.
     if (a.is_favourite != b.is_favourite)
@@ -171,7 +170,7 @@ public class Contacts.ContactList : ListBox {
   }
 
   private void update_header (ListBoxRow row, ListBoxRow? before) {
-    var current = ((ContactDataRow) row).contact.individual;
+    var current = ((ContactDataRow) row).individual;
 
     if (before == null) {
       if (current.is_favourite)
@@ -181,7 +180,7 @@ public class Contacts.ContactList : ListBox {
       return;
     }
 
-    var previous = ((ContactDataRow) before).contact.individual;
+    var previous = ((ContactDataRow) before).individual;
     if (!current.is_favourite && previous.is_favourite) {
       row.set_header (create_header_label (_("All Contacts")));
     } else {
@@ -203,12 +202,17 @@ public class Contacts.ContactList : ListBox {
     return label;
   }
 
-  private void contact_added_cb (Store store, Contact c) {
-    var row =  new ContactDataRow(c);
-    row.selector_button.toggled.connect ( () => { on_row_checkbox_toggled (row); });
-    row.selector_button.visible = (this.state == UiState.SELECTING);
+  private void contact_added_cb (Store store, Individual i) {
+    // Don't create a row for ignorable contacts
+    if (!Contacts.Utils.is_ignorable (i)) {
+      var row =  new ContactDataRow (i);
+      row.selector_button.toggled.connect ( () => { on_row_checkbox_toggled (row); });
+      row.selector_button.visible = (this.state == UiState.SELECTING);
 
-    add (row);
+      add (row);
+    } else {
+      debug ("Contact %s was ignored", i.id);
+    }
   }
 
   private void on_row_checkbox_toggled (ContactDataRow row) {
@@ -226,57 +230,77 @@ public class Contacts.ContactList : ListBox {
     contacts_marked (this.nr_contacts_marked);
   }
 
-  private void contact_removed_cb (Store store, Contact c) {
-    var row = find_row_for_contact (c);
+  private void contact_removed_cb (Store store, Individual i) {
+    var row = find_row_for_contact (i);
     if (row != null)
       row.destroy ();
   }
 
   public override void row_selected (ListBoxRow? row) {
     var data = (ContactDataRow?) row as ContactDataRow;
-    var contact = data != null ? data.contact : null;
-    selection_changed (contact);
+    var individual = data != null ? data.individual : null;
+    selection_changed (individual);
 #if HAVE_TELEPATHY
-    if (contact != null)
-      contact.fetch_contact_info ();
+    if (individual != null)
+      Contact.fetch_contact_info (individual);
 #endif
   }
 
   private bool filter_row (ListBoxRow row) {
-    var contact = ((ContactDataRow) row).contact;
-    return !contact.hidden && this.filter_query.is_match (contact.individual) > 0;
+    var individual = ((ContactDataRow) row).individual;
+    return this.filter_query.is_match (individual) > 0;
   }
 
-  public void select_contact (Contact? contact) {
-    if (contact == null) {
+  public void select_contact (Individual? individual) {
+    if (individual == null) {
       /* deselect */
       select_row (null);
       return;
     }
 
-    select_row (find_row_for_contact (contact));
+    select_row (find_row_for_contact (individual));
   }
 
-  private ContactDataRow? find_row_for_contact (Contact contact) {
+  public void hide_contact (Individual? individual) {
+    if (individual != null) {
+      find_row_for_contact (individual).hide ();
+    }
+  }
+
+
+  private ContactDataRow? find_row_for_contact (Individual individual) {
     foreach (var widget in get_children ()) {
       var row = ((ContactDataRow) widget);
-      if (row.contact == contact)
+      if (row.individual == individual)
         return row;
     }
 
     return null;
   }
 
-  public LinkedList<Contact> get_marked_contacts () {
-    var cs = new LinkedList<Contact> ();
+  public LinkedList<Individual> get_marked_contacts () {
+    var cs = new LinkedList<Individual> ();
     foreach (var widget in get_children ()) {
       var row = widget as ContactDataRow;
       if (row.selector_button.active)
-        cs.add (row.contact);
+        cs.add (row.individual);
+    }
+    return cs;
+  }
+
+  public LinkedList<Individual> get_marked_contacts_and_hide () {
+    var cs = new LinkedList<Individual> ();
+    foreach (var widget in get_children ()) {
+      var row = widget as ContactDataRow;
+      if (row.selector_button.active) {
+        row.visible = false;
+        cs.add (row.individual);
+      }
     }
     return cs;
   }
 
+
   public override bool button_press_event (Gdk.EventButton event) {
     base.button_press_event (event);
 
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index 0e11e72..9e048ef 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -33,7 +33,7 @@ public class Contacts.ContactPane : Stack {
 
   private Store store;
 
-  public Contact? contact = null;
+  public Individual? individual = null;
 
   [GtkChild]
   private Grid none_selected_page;
@@ -66,7 +66,7 @@ public class Contacts.ContactPane : Stack {
 
   /* Signals */
   public signal void contacts_linked (string? main_contact, string linked_contact, LinkOperation operation);
-  public signal void will_delete (Contact contact);
+  public signal void will_delete (Individual individual);
   /**
    * Passes the changed display name to all listeners after edit mode has been completed.
    */
@@ -80,16 +80,16 @@ public class Contacts.ContactPane : Stack {
     this.edit_contact_actions.add_action_entries (action_entries, this);
   }
 
-  public void add_suggestion (Contact c) {
+  public void add_suggestion (Individual i) {
     var parent_overlay = this.get_parent () as Overlay;
 
     remove_suggestion_grid ();
-    this.suggestion_grid = new LinkSuggestionGrid (c);
+    this.suggestion_grid = new LinkSuggestionGrid (i);
     parent_overlay.add_overlay (this.suggestion_grid);
 
     this.suggestion_grid.suggestion_accepted.connect ( () => {
-        var linked_contact = c.individual.display_name;
-        link_contacts.begin (contact, c, this.store, (obj, result) => {
+        var linked_contact = this.individual.display_name;
+        link_contacts.begin (this.individual, i, this.store, (obj, result) => {
             var operation = link_contacts.end (result);
             this.contacts_linked (null, linked_contact, operation);
           });
@@ -98,18 +98,18 @@ public class Contacts.ContactPane : Stack {
 
     this.suggestion_grid.suggestion_rejected.connect ( () => {
         /* TODO: Add undo */
-        store.add_no_suggest_link (contact, c);
+        store.add_no_suggest_link (this.individual, i);
         remove_suggestion_grid ();
       });
   }
 
-  public void show_contact (Contact? contact) {
-    if (this.contact == contact)
+  public void show_contact (Individual? individual) {
+    if (this.individual == individual)
       return;
 
-    this.contact = contact;
+    this.individual = individual;
 
-    if (this.contact != null) {
+    if (this.individual != null) {
       show_contact_sheet ();
     } else {
       remove_contact_sheet ();
@@ -118,18 +118,17 @@ public class Contacts.ContactPane : Stack {
   }
 
   private void show_contact_sheet () {
-    assert (this.contact != null);
+    assert (this.individual != null);
 
     remove_contact_sheet();
-    this.sheet = new ContactSheet (this.contact, this.store);
+    this.sheet = new ContactSheet (this.individual, this.store);
     this.contact_sheet_page.add (this.sheet);
     set_visible_child (this.contact_sheet_page);
 
-    var matches = this.store.aggregator.get_potential_matches (this.contact.individual, MatchResult.HIGH);
-    foreach (var ind in matches.keys) {
-      var c = Contact.from_individual (ind);
-      if (c != null && this.contact.suggest_link_to (c)) {
-        add_suggestion (c);
+    var matches = this.store.aggregator.get_potential_matches (this.individual, MatchResult.HIGH);
+    foreach (var i in matches.keys) {
+      if (i != null && Contacts.Utils.suggest_link_to (this.store, this.individual, i)) {
+        add_suggestion (i);
         break;
       }
     }
@@ -151,7 +150,8 @@ public class Contacts.ContactPane : Stack {
     if (this.editor != null)
       remove_contact_editor ();
 
-    this.editor = new ContactEditor (this.contact, this.store, this.edit_contact_actions);
+    this.editor = new ContactEditor (this.individual, this.store, this.edit_contact_actions);
+
     this.editor.linked_button.clicked.connect (linked_accounts);
     this.editor.remove_button.clicked.connect (delete_contact);
 
@@ -183,14 +183,14 @@ public class Contacts.ContactPane : Stack {
     var tok = action.name.split (".");
 
     if (tok[0] == "add") {
-      editor.add_new_row_for_property (contact.find_primary_persona (),
+      editor.add_new_row_for_property (Contacts.Utils.find_primary_persona (individual),
                                       tok[1],
                                       tok.length > 2 ? tok[2].up () : null);
     }
   }
 
   private void linked_accounts () {
-    var dialog = new LinkedPersonasDialog (this.parent_window, this.store, contact);
+    var dialog = new LinkedPersonasDialog (this.parent_window, this.store, individual);
     if (dialog.run () == ResponseType.CLOSE && dialog.any_unlinked) {
       /* update edited contact if any_unlinked */
       stop_editing ();
@@ -200,14 +200,13 @@ public class Contacts.ContactPane : Stack {
   }
 
   void delete_contact () {
-    if (contact != null) {
-      contact.hidden = true;
-      this.will_delete (contact);
+    if (individual != null) {
+      will_delete (individual);
     }
   }
 
   public void start_editing() {
-    if (this.on_edit_mode || this.contact == null)
+    if (this.on_edit_mode || this.individual == null)
       return;
 
     this.on_edit_mode = true;
@@ -228,7 +227,7 @@ public class Contacts.ContactPane : Stack {
 
     remove_contact_editor ();
 
-    if (this.contact != null)
+    if (this.individual != null)
       show_contact_sheet ();
     else
       set_visible_child (this.none_selected_page);
@@ -237,7 +236,7 @@ public class Contacts.ContactPane : Stack {
   private async void save_editor_changes () {
     foreach (var prop in this.editor.properties_changed ().entries) {
       try {
-        yield Contact.set_persona_property (prop.value.persona, prop.key, prop.value.value);
+        yield Contacts.Utils.set_persona_property (prop.value.persona, prop.key, prop.value.value);
       } catch (Error e) {
         show_message (e.message);
       }
@@ -246,7 +245,7 @@ public class Contacts.ContactPane : Stack {
     if (this.editor.name_changed ()) {
       var v = this.editor.get_full_name_value ();
       try {
-        yield this.contact.set_individual_property ("full-name", v);
+        yield Contacts.Utils.set_individual_property (individual, "full-name", v);
         display_name_changed (v.get_string ());
       } catch (Error e) {
         show_message (e.message);
@@ -256,7 +255,7 @@ public class Contacts.ContactPane : Stack {
     if (this.editor.avatar_changed ()) {
       var v = this.editor.get_avatar_value ();
       try {
-        yield this.contact.set_individual_property ("avatar", v);
+        yield Contacts.Utils.set_individual_property (individual, "avatar", v);
       } catch (Error e) {
         show_message (e.message);
       }
@@ -265,7 +264,7 @@ public class Contacts.ContactPane : Stack {
 
   public void new_contact () {
     this.on_edit_mode = true;
-    this.contact = null;
+    this.individual = null;
     remove_contact_sheet ();
     create_contact_editor ();
     set_visible_child (this.contact_editor_page);
@@ -298,22 +297,33 @@ public class Contacts.ContactPane : Stack {
       return;
     }
 
+    // Create a FakeContact temporary persona so we can show it already to the user
+    var fake_persona = new FakePersona (FakePersonaStore.the_store(), details);
+    var fake_personas = new HashSet<Persona> ();
+    fake_personas.add (fake_persona);
+    var fake_individual = new Individual(fake_personas);
+    this.parent_window.set_shown_contact (fake_individual);
+
     // Create the contact
     var primary_store = this.store.aggregator.primary_store;
     Persona? persona = null;
     try {
-      persona = yield Contact.create_primary_persona_for_details (primary_store, details);
+      persona = yield primary_store.add_persona_from_details (details);
     } catch (Error e) {
       show_message_dialog (_("Unable to create new contacts: %s").printf (e.message));
+      this.parent_window.set_shown_contact (null);
       return;
     }
 
-    // Now show it to the user
-    var contact = this.store.find_contact_with_persona (persona);
-    if (contact != null)
-      this.parent_window.set_shown_contact (contact);
-    else
+    // Now show the real persona to the user
+    var individual = persona.individual;
+    if (individual != null) {
+      //FIXME: This causes a flicker, especially visibile when a avatar is set
+      this.parent_window.set_shown_contact (individual);
+    } else {
       show_message_dialog (_("Unable to find newly created contact"));
+      this.parent_window.set_shown_contact (null);
+    }
   }
 
   private void show_message_dialog (string message) {
diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala
index 62f2e3f..aaf4e8b 100644
--- a/src/contacts-contact-sheet.vala
+++ b/src/contacts-contact-sheet.vala
@@ -25,16 +25,15 @@ using Gee;
  * (Note: to edit a contact, use the {@link ContactEditor} instead.
  */
 public class Contacts.ContactSheet : ContactForm {
+  public ContactSheet (Individual individual, Store store) {
+    this.individual = individual;
+    this.store = store;
 
-  public ContactSheet (Contact contact, Store store) {
-      this.contact = contact;
-      this.store = store;
+    this.individual.notify.connect (update);
+    this.individual.personas_changed.connect (update);
+    this.store.quiescent.connect (update);
 
-      this.contact.individual.notify.connect (update);
-      this.contact.individual.personas_changed.connect (update);
-      this.store.quiescent.connect (update);
-
-      update ();
+    update ();
   }
 
   private Button add_row_with_button (string label, string value, bool use_link_button = false) {
@@ -87,7 +86,7 @@ public class Contacts.ContactSheet : ContactForm {
     this.last_row = 0;
     this.container_grid.foreach ((child) => this.container_grid.remove (child));
 
-    var image_frame = new Avatar (PROFILE_SIZE, this.contact);
+    var image_frame = new Avatar (PROFILE_SIZE, this.individual);
     image_frame.set_vexpand (false);
     image_frame.set_valign (Align.START);
     this.container_grid.attach (image_frame,  0, 0, 1, 3);
@@ -96,7 +95,7 @@ public class Contacts.ContactSheet : ContactForm {
 
     this.last_row += 3; // Name/Avatar takes up 3 rows
 
-    var personas = this.contact.get_personas_for_display ();
+    var personas = Contacts.Utils.get_personas_for_display (this.individual);
     /* Cause personas are sorted properly I can do this */
     foreach (var p in personas) {
       bool is_first_persona = (this.last_row == 3);
@@ -122,7 +121,7 @@ public class Contacts.ContactSheet : ContactForm {
 
   private void update_name_label (Gtk.Label name_label) {
     var name = Markup.printf_escaped ("<span font='16'>%s</span>",
-                                      this.contact.individual.display_name);
+                                      this.individual.display_name);
     name_label.set_markup (name);
   }
 
@@ -133,9 +132,9 @@ public class Contacts.ContactSheet : ContactForm {
     name_label.selectable = true;
     this.container_grid.attach (name_label,  1, 0, 1, 3);
     update_name_label (name_label);
-    this.contact.individual.notify["display-name"].connect ((obj, spec) => {
-        update_name_label (name_label);
-      });
+    this.individual.notify["display-name"].connect ((obj, spec) => {
+      update_name_label (name_label);
+    });
   }
 
   private void add_row_for_property (Persona persona, string property) {
@@ -173,11 +172,11 @@ public class Contacts.ContactSheet : ContactForm {
   private void add_emails (Persona persona) {
     var details = persona as EmailDetails;
     if (details != null) {
-      var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
+      var emails = Contacts.Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
       foreach (var email in emails) {
           var button = add_row_with_button (TypeSet.email.format_type (email), email.value);
           button.clicked.connect (() => {
-          Utils.compose_mail ("%s <%s>".printf(this.contact.individual.display_name, email.value));
+          Utils.compose_mail ("%s <%s>".printf(this.individual.display_name, email.value));
         });
       }
     }
@@ -186,7 +185,7 @@ public class Contacts.ContactSheet : ContactForm {
   private void add_phone_nrs (Persona persona) {
     var phone_details = persona as PhoneDetails;
     if (phone_details != null) {
-      var phones = Contact.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
+      var phones = Contacts.Utils.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
       foreach (var phone in phones) {
 #if HAVE_TELEPATHY
         if (this.store.caller_account != null) {
@@ -213,13 +212,12 @@ public class Contacts.ContactSheet : ContactForm {
           if (persona is Tpf.Persona) {
             var button = add_row_with_button (ImService.get_display_name (protocol), id.value);
             button.clicked.connect (() => {
-                var im_persona = this.contact.find_im_persona (protocol, id.value);
-                if (im_persona != null) {
-                  var type = im_persona.presence_type;
-                  if (type != PresenceType.UNSET && type != PresenceType.ERROR &&
-                      type != PresenceType.OFFLINE && type != PresenceType.UNKNOWN) {
-                    Utils.start_chat (this.contact, protocol, id.value);
-                  }
+              var im_persona = Contacts.Utils.find_im_persona (individual, protocol, id.value);
+              if (im_persona != null) {
+                var type = im_persona.presence_type;
+                if (type != PresenceType.UNSET && type != PresenceType.ERROR &&
+                    type != PresenceType.OFFLINE && type != PresenceType.UNKNOWN) {
+                  Utils.start_chat (this.contact, protocol, id.value);
                 }
               });
           }
@@ -261,7 +259,7 @@ public class Contacts.ContactSheet : ContactForm {
     var addr_details = persona as PostalAddressDetails;
     if (addr_details != null) {
       foreach (var addr in addr_details.postal_addresses) {
-        var all_strs = string.joinv ("\n", Contact.format_address (addr.value));
+        var all_strs = string.joinv ("\n", Contacts.Utils.format_address (addr.value));
         add_row_with_label (TypeSet.general.format_type (addr), all_strs);
       }
     }
diff --git a/src/contacts-fake-persona-store.vala b/src/contacts-fake-persona-store.vala
index 61532b4..471a16c 100644
--- a/src/contacts-fake-persona-store.vala
+++ b/src/contacts-fake-persona-store.vala
@@ -64,36 +64,24 @@ public class Contacts.FakePersonaStore : PersonaStore {
 }
 
 /**
- * A "dummy" Persona which is used when creating a new contact (to store
- * information).
+ * A "dummy" Persona which is used when creating a new contact
+ * The FakePersona is used as a placeholder till we get the real persona from folks
+ * It needs to implement all Details we support so that we don't loise any information
  */
-public class Contacts.FakePersona : Persona {
-  public Contact contact;
-  private class PropVal {
-    public string property;
-    public Value value;
-  }
-  private ArrayList<PropVal> prop_vals;
-  private bool now_real;
-  private bool has_full_name;
-
-  public static FakePersona? maybe_create_for (Store store, Contact contact) {
-    var primary_persona = contact.find_primary_persona ();
-
-    if (primary_persona != null)
-      return null;
-
-    foreach (var p in contact.individual.personas) {
-      // Don't fake a primary persona if we have an eds
-      // persona on a non-readonly store
-      if (p.store.type_id == "eds" &&
-          p.store.can_add_personas == MaybeBool.TRUE &&
-          p.store.can_remove_personas == MaybeBool.TRUE)
-        return null;
-    }
-
-    return new FakePersona (store, contact);
-  }
+public class Contacts.FakePersona : Persona,
+  AvatarDetails,
+  BirthdayDetails,
+  EmailDetails,
+  ImDetails,
+  NameDetails,
+  NoteDetails,
+  PhoneDetails,
+  UrlDetails,
+  PostalAddressDetails
+{
+  private HashTable<string, Value?> properties;
+  // Keep track of the persona in the actual store
+  private weak Persona real_persona { get; set; default = null; }
 
   private const string[] _linkable_properties = {};
   private const string[] _writeable_properties = {};
@@ -105,39 +93,168 @@ public class Contacts.FakePersona : Persona {
     get { return _writeable_properties; }
   }
 
-  public FakePersona (Store? store, Contact contact) {
-    Object (display_id: "display_id",
+  [CCode (notify = false)]
+  public LoadableIcon? avatar
+  {
+    get { unowned Value? value = this.properties.get ("avatar");
+      if (value == null)
+        return null;
+      return (LoadableIcon?) value;
+    }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public string full_name
+  {
+    get { unowned Value? value = this.properties.get ("full-name");
+      if (value == null)
+        return "";
+      return value.get_string (); }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public string nickname
+  {
+    get { unowned Value? value = this.properties.get ("nickname");
+      if (value == null)
+        return "";
+      return value.get_string (); }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public StructuredName? structured_name
+  {
+    get { return null; }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public Set<PhoneFieldDetails> phone_numbers
+  {
+    get { unowned Value? value = this.properties.get ("phone-numbers");
+      if (value == null) {
+        var new_value = Value (typeof (Set));
+        new_value.set_object (new HashSet<PhoneFieldDetails> ());
+        this.properties.set ("phone-numbers", new_value);
+        value = this.properties.get ("phone-numbers");
+      }
+      return (Set<PhoneFieldDetails>) value;
+    }
+
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public Set<UrlFieldDetails> urls
+  {
+    get { unowned Value? value = this.properties.get ("urls");
+      if (value == null) {
+        var new_value = Value (typeof (Set));
+        new_value.set_object (new HashSet<UrlFieldDetails> ());
+        this.properties.set ("urls", new_value);
+        value = this.properties.get ("urls");
+      }
+      return (Set<UrlFieldDetails>) value;
+    }
+
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public Set<PostalAddressFieldDetails> postal_addresses
+  {
+    get { unowned Value? value = this.properties.get ("urls");
+      if (value == null) {
+        var new_value = Value (typeof (Set));
+        new_value.set_object (new HashSet<PostalAddressFieldDetails> ());
+        this.properties.set ("urls", new_value);
+        value = new_value;
+      }
+
+      return (Set<PostalAddressFieldDetails>) value;
+    }
+
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public Set<NoteFieldDetails> notes
+  {
+    get { unowned Value? value = this.properties.get ("notes");
+      if (value == null) {
+        var new_value = Value (typeof (Set));
+        new_value.set_object (new HashSet<NoteFieldDetails> ());
+        this.properties.set ("notes", new_value);
+        value = new_value;
+      }
+      return (Set<NoteFieldDetails>) value;
+    }
+
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public DateTime? birthday
+  {
+    get { unowned Value? value = this.properties.get ("birthday");
+      if (value == null)
+        return null;
+      return (DateTime) value;
+    }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public string? calendar_event_id
+  {
+    get { return null; }
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public MultiMap<string,ImFieldDetails> im_addresses
+  {
+    get { unowned Value? value = this.properties.get ("im-addresses");
+      if (value == null) {
+        var new_value = Value (typeof (MultiMap));
+        new_value.set_object (new HashMultiMap<string, ImFieldDetails> ());
+        this.properties.set ("im-addresses", new_value);
+        value = new_value;
+      }
+
+      return (MultiMap<string, ImFieldDetails>) value;
+    }
+
+    set {}
+  }
+
+  [CCode (notify = false)]
+  public Set<EmailFieldDetails> email_addresses
+  {
+    get { unowned Value? value = this.properties.get ("email-addresses");
+      if (value == null) {
+        var new_value = Value (typeof (Set));
+        new_value.set_object (new HashSet<EmailFieldDetails> ());
+        this.properties.set ("email-addresses", new_value);
+        value = new_value;
+      }
+
+      return (Set<EmailFieldDetails>) value;
+    }
+    set {}
+  }
+
+  public FakePersona (PersonaStore store, HashTable<string, Value?> details) {
+    //TODO: use correct data to fill the object
+    Object (display_id: "display-id-fake-persona",
             uid: "uid-fake-persona",
             iid: "iid",
-            store: store.aggregator.primary_store ?? FakePersonaStore.the_store(),
+            store: store,
             is_user: false);
-    this.contact = contact;
-    this.contact.fake_persona = this;
-  }
-
-  public async Persona? make_real_and_set (string property,
-                                           Value value) throws IndividualAggregatorError, ContactError, 
PropertyError {
-    var v = new PropVal ();
-    v.property = property;
-    v.value = value;
-    if (property == "full-name")
-      has_full_name = true;
-
-    if (prop_vals == null) {
-      prop_vals = new ArrayList<PropVal> ();
-      prop_vals.add (v);
-      Persona p = yield contact.ensure_primary_persona ();
-      if (!has_full_name)
-        p.set ("full-name", contact.individual.display_name);
-      foreach (var pv in prop_vals) {
-        yield Contact.set_persona_property (p, pv.property, pv.value);
-      }
-      now_real = true;
-      return p;
-    }
 
-    assert (!now_real);
-    prop_vals.add (v);
-    return null;
+    this.properties = details;
   }
 }
diff --git a/src/contacts-link-suggestion-grid.vala b/src/contacts-link-suggestion-grid.vala
index 998c5eb..99846b1 100644
--- a/src/contacts-link-suggestion-grid.vala
+++ b/src/contacts-link-suggestion-grid.vala
@@ -40,21 +40,23 @@ public class Contacts.LinkSuggestionGrid : Grid {
   public signal void suggestion_accepted ();
   public signal void suggestion_rejected ();
 
-  public LinkSuggestionGrid (Contact contact) {
+  public LinkSuggestionGrid (Individual individual) {
     get_style_context ().add_class ("contacts-suggestion");
 
-    var image_frame = new Avatar (AVATAR_SIZE, contact);
+    var image_frame = new Avatar (AVATAR_SIZE, individual);
     image_frame.hexpand = false;
     image_frame.margin = 12;
     image_frame.show ();
     attach (image_frame, 0, 0, 1, 2);
 
     this.description_label.xalign = 0;
-    this.description_label.label = contact.is_main?
-          _("Is this the same person as %s from %s?").printf (contact.individual.display_name, 
contact.format_persona_stores ())
-        : _("Is this the same person as %s?").printf (contact.individual.display_name);
+    this.description_label.label = Contacts.Utils.has_main_persona (individual) ?
+      _("Is this the same person as %s from %s?")
+       .printf (individual.display_name,
+                Contacts.Utils.format_persona_stores (individual))
+      : _("Is this the same person as %s?").printf (individual.display_name);
 
-    var extra_info = find_extra_description (contact);
+    var extra_info = find_extra_description (individual);
     if (extra_info != null) {
       this.extra_info_label.show ();
       this.extra_info_label.label = extra_info;
@@ -64,24 +66,24 @@ public class Contacts.LinkSuggestionGrid : Grid {
     this.accept_button.clicked.connect ( () => suggestion_accepted ());
   }
 
-  private string? find_extra_description (Contact contact) {
+  private string? find_extra_description (Individual individual) {
     // First try an email address
-    var emails = contact.individual.email_addresses;
+    var emails = individual.email_addresses;
     if (!emails.is_empty)
       return Utils.get_first<EmailFieldDetails> (emails).value;
 
     // Maybe a website? Works well with e.g. social media profiles
-    var urls = contact.individual.urls;
+    var urls = individual.urls;
     if (!urls.is_empty)
       return Utils.get_first<UrlFieldDetails> (urls).value;
 
     // Try a phone number
-    var phones = contact.individual.phone_numbers;
+    var phones = individual.phone_numbers;
     if (!phones.is_empty)
       return Utils.get_first<PhoneFieldDetails> (phones).value;
 
     // A postal address maybe?
-    var addresses = contact.individual.postal_addresses;
+    var addresses = individual.postal_addresses;
     if (!addresses.is_empty)
       return Utils.get_first<PostalAddressFieldDetails> (addresses).value.to_string ();
 
diff --git a/src/contacts-linked-personas-dialog.vala b/src/contacts-linked-personas-dialog.vala
index be3e0e1..6af7aef 100644
--- a/src/contacts-linked-personas-dialog.vala
+++ b/src/contacts-linked-personas-dialog.vala
@@ -25,22 +25,22 @@ public class Contacts.LinkedPersonasDialog : Dialog {
   [GtkChild]
   private ListBox linked_accounts_view;
 
-  private Contact contact;
+  private Individual individual;
 
   public bool any_unlinked = false;
 
-  public LinkedPersonasDialog (Window main_win, Store store, Contact contact) {
+  public LinkedPersonasDialog (Window main_win, Store store, Individual individual) {
     Object (
       use_header_bar: 1,
       transient_for: main_win,
-      title: contact.individual.display_name
+      title: individual.display_name
     );
 
-    this.contact = contact;
+    this.individual = individual;
     this.linked_accounts_view.set_header_func (add_separator);
 
     // loading personas for display
-    var personas = contact.get_personas_for_display ();
+    var personas = Contacts.Utils.get_personas_for_display (individual);
     bool is_first = true;
     foreach (var p in personas) {
       if (is_first) {
@@ -50,7 +50,7 @@ public class Contacts.LinkedPersonasDialog : Dialog {
 
       var row_grid = new Grid ();
 
-      var image_frame = new Avatar (AVATAR_SIZE, contact);
+      var image_frame = new Avatar (AVATAR_SIZE, individual);
       image_frame.set_hexpand (false);
       image_frame.margin = 6;
       image_frame.margin_end = 12;
@@ -64,7 +64,7 @@ public class Contacts.LinkedPersonasDialog : Dialog {
 
       row_grid.attach (display_name, 1, 0, 1, 1);
 
-      var store_name = new Label (Contact.format_persona_store_name_for_contact (p));
+      var store_name = new Label (Contacts.Utils.format_persona_store_name_for_contact (p));
       store_name.set_halign (Align.START);
       store_name.set_valign (Align.START);
       store_name.set_hexpand (true);
@@ -79,7 +79,7 @@ public class Contacts.LinkedPersonasDialog : Dialog {
 
       /* signal */
       button.clicked.connect (() => {
-          unlink_persona.begin (store, contact, p, (obj, result) => {
+          unlink_persona.begin (store, individual, p, (obj, result) => {
               unlink_persona.end (result);
 
               row_grid.destroy ();
diff --git a/src/contacts-linking.vala b/src/contacts-linking.vala
index 999d514..76043de 100644
--- a/src/contacts-linking.vala
+++ b/src/contacts-linking.vala
@@ -28,7 +28,7 @@ namespace Contacts {
       public Object old_value;
     }
     private Persona? _added_persona;
-    private Contact? main_contact;
+    private Individual? main_contact;
     private ArrayList<Persona>? split_out_personas;
     ArrayList<Change> changes;
 
@@ -36,14 +36,14 @@ namespace Contacts {
       changes = new ArrayList<Change> ();
     }
 
-    public void set_main_contact (Contact? contact) {
-      main_contact = contact;
+    public void set_main_contact (Individual? individual) {
+      main_contact = individual;
     }
 
-    public void set_split_out_contact (Contact? contact) {
-      if (contact != null) {
+    public void set_split_out_contact (Individual? individual) {
+      if (individual != null) {
        split_out_personas = new ArrayList<Persona> ();
-       split_out_personas.add_all (contact.individual.personas);
+       split_out_personas.add_all (individual.personas);
       }
     }
 
@@ -521,7 +521,7 @@ namespace Contacts {
     }
   }
 
-  public async LinkOperation link_contacts (Contact main, Contact? other, Store contacts_store) {
+  public async LinkOperation link_contacts (Individual main, Individual? other, Store contacts_store) {
     // This should not be used as being replaced with the new individual
     // instead we should always pick this contact to keep around
     main.set_data ("contacts-master-at-join", true);
@@ -529,10 +529,10 @@ namespace Contacts {
     var operation = new LinkOperation ();
     operation.set_split_out_contact (other);
 
-    var main_linkables = get_linkable_attributes_for_individual (main.individual);
+    var main_linkables = get_linkable_attributes_for_individual (main);
     Set<PersonaAttribute>? other_linkables = null;
     if (other != null)
-      other_linkables = get_linkable_attributes_for_individual (other.individual);
+      other_linkables = get_linkable_attributes_for_individual (other);
     Set<PersonaAttribute>? linkables = null;
 
     // Remove all linkable data from each contact that is already in the other contact
@@ -542,7 +542,7 @@ namespace Contacts {
     }
 
     Persona? write_persona = null;
-    foreach (var p1 in main.individual.personas) {
+    foreach (var p1 in main.personas) {
       if (other_linkables != null &&
          persona_can_link_to (p1, other_linkables)) {
        write_persona = p1;
@@ -554,7 +554,7 @@ namespace Contacts {
 
     if (other != null &&
        (write_persona == null || !write_persona.store.is_primary_store)) {
-      foreach (var p2 in other.individual.personas) {
+      foreach (var p2 in other.personas) {
        if (persona_can_link_to (p2, main_linkables)) {
          // Only override main persona if its a primary store persona
          if (write_persona == null || p2.store.is_primary_store) {
@@ -571,9 +571,9 @@ namespace Contacts {
       var details = new HashTable<string, Value?> (str_hash, str_equal);
       try {
        var v = Value (typeof (string));
-       v.set_string (main.individual.display_name);
+       v.set_string (main.display_name);
        details.set ("full-name", v);
-       write_persona = yield Contact.create_primary_persona_for_details 
(contacts_store.aggregator.primary_store, details);
+       write_persona = yield contacts_store.aggregator.primary_store.add_persona_from_details (details);
        operation.added_persona (write_persona);
        linkables = main_linkables;
        if (other_linkables != null)
@@ -592,13 +592,12 @@ namespace Contacts {
     return operation;
   }
 
-  public async LinkOperation unlink_persona (Store store, Contact contact, Persona persona_to_unlink) {
-    var individual = contact.individual;
+  public async LinkOperation unlink_persona (Store store, Individual individual, Persona persona_to_unlink) {
     var persona_to_unlink_removals = PersonaAttribute.create_set ();
     var other_personas_removals = PersonaAttribute.create_set ();
 
     var operation = new LinkOperation ();
-    operation.set_main_contact (contact);
+    operation.set_main_contact (individual);
 
     foreach (PersonaAttribute a1 in get_linkable_attributes (persona_to_unlink)) {
       // Check that this attribute actually is used to link this persona to the individual
@@ -677,7 +676,7 @@ namespace Contacts {
       var details = new HashTable<string, Value?> (str_hash, str_equal);
       try {
         main_persona = yield store.aggregator.primary_store.add_persona_from_details (details);
-        yield (main_persona as NameDetails).change_full_name (contact.individual.display_name);
+        yield (main_persona as NameDetails).change_full_name (individual.display_name);
         operation.added_persona (main_persona);
       } catch (GLib.Error e) {
        warning ("Unable to create new persona when unlinking: %s\n", e.message);
@@ -752,12 +751,12 @@ namespace Contacts {
     }
   }
 
-  public async LinkOperation2 link_contacts_list (LinkedList<Contact> contact_list, Store contacts_store) {
+  public async LinkOperation2 link_contacts_list (LinkedList<Individual> contact_list, Store contacts_store) 
{
     var operation = new LinkOperation2 (contacts_store);
 
     var all_personas = new HashSet<Persona> ();
-    foreach (var c in contact_list) {
-      var ps = c.individual.personas;
+    foreach (var i in contact_list) {
+      var ps = i.personas;
       all_personas.add_all (ps);
       operation.add_persona_set (ps);
     }
diff --git a/src/contacts-list-pane.vala b/src/contacts-list-pane.vala
index 3aaf3d6..7aeced5 100644
--- a/src/contacts-list-pane.vala
+++ b/src/contacts-list-pane.vala
@@ -43,9 +43,9 @@ public class Contacts.ListPane : Frame {
 
   public UiState state { get; set; }
 
-  public signal void selection_changed (Contact? contact);
-  public signal void link_contacts (LinkedList<Contact> contacts);
-  public signal void delete_contacts (LinkedList<Contact> contacts);
+  public signal void selection_changed (Individual? individual);
+  public signal void link_contacts (LinkedList<Individual> individual);
+  public signal void delete_contacts (LinkedList<Individual> individual);
   public signal void contacts_marked (int contacts_marked);
 
   public ListPane (Settings settings, Store contacts_store) {
@@ -64,8 +64,8 @@ public class Contacts.ListPane : Frame {
     bind_property ("state", this.contacts_list, "state", BindingFlags.BIDIRECTIONAL | 
BindingFlags.SYNC_CREATE);
     this.contacts_list_container.add (this.contacts_list);
 
-    this.contacts_list.selection_changed.connect( (l, contact) => {
-        selection_changed (contact);
+    this.contacts_list.selection_changed.connect( (l, individual) => {
+        selection_changed (individual);
       });
 
     this.contacts_list.contacts_marked.connect ((nr_contacts_marked) => {
@@ -75,6 +75,10 @@ public class Contacts.ListPane : Frame {
       });
   }
 
+  public void undo_deletion () {
+    contacts_list.show_all ();
+  }
+
   private void on_ui_state_changed (Object obj, ParamSpec pspec) {
     // Disable when editing a contact. (Not using `this.sensitive` to allow scrolling)
     this.filter_entry.sensitive
@@ -89,8 +93,12 @@ public class Contacts.ListPane : Frame {
     this.filter_query.query_string = this.filter_entry.text;
   }
 
-  public void select_contact (Contact? contact) {
-    this.contacts_list.select_contact (contact);
+  public void select_contact (Individual? individual) {
+    this.contacts_list.select_contact (individual);
+  }
+  
+  public void hide_contact (Individual? individual) {
+    this.contacts_list.hide_contact (individual);
   }
 
   [GtkCallback]
@@ -100,10 +108,7 @@ public class Contacts.ListPane : Frame {
 
   [GtkCallback]
   private void on_delete_button_clicked (Gtk.Button delete_button) {
-    var marked_contacts = this.contacts_list.get_marked_contacts ();
-    foreach (var c in marked_contacts)
-      c.hidden = true;
-    delete_contacts (marked_contacts);
+    delete_contacts (this.contacts_list.get_marked_contacts_and_hide ());
   }
 
   /* Limiting width hack */
diff --git a/src/contacts-store.vala b/src/contacts-store.vala
index 1a033fb..9334900 100644
--- a/src/contacts-store.vala
+++ b/src/contacts-store.vala
@@ -20,14 +20,13 @@ using Folks;
 using Gee;
 
 public class Contacts.Store : GLib.Object {
-  public signal void added (Contact c);
-  public signal void removed (Contact c);
+  public signal void added (Individual c);
+  public signal void removed (Individual c);
   public signal void quiescent ();
   public signal void prepared ();
 
   public IndividualAggregator aggregator { get; private set; }
   public BackendStore backend_store { get { return this.aggregator.backend_store; } }
-  private ArrayList<Contact> contacts;
 
   public Gee.HashMultiMap<string, string> dont_suggest_link;
 
@@ -43,11 +42,6 @@ public class Contacts.Store : GLib.Object {
     get { return this.aggregator.is_prepared; }
   }
 
-  public void refresh () {
-    foreach (var c in contacts)
-      c.update ();
-  }
-
   private bool individual_can_replace_at_split (Individual new_individual) {
     foreach (var p in new_individual.personas) {
       if (p.get_data<bool> ("contacts-new-contact"))
@@ -57,7 +51,7 @@ public class Contacts.Store : GLib.Object {
   }
 
   private bool individual_should_replace_at_join (Individual old_individual) {
-    var c = Contact.from_individual (old_individual);
+    var c = old_individual;
     return c.get_data<bool> ("contacts-master-at-join");
   }
 
@@ -99,18 +93,18 @@ public class Contacts.Store : GLib.Object {
     }
   }
 
-  public bool may_suggest_link (Contact a, Contact b) {
-    foreach (var a_persona in a.individual.personas) {
+  public bool may_suggest_link (Individual a, Individual b) {
+    foreach (var a_persona in a.personas) {
       foreach (var no_link_uid in dont_suggest_link.get (a_persona.uid)) {
-       foreach (var b_persona in b.individual.personas) {
+       foreach (var b_persona in b.personas) {
          if (b_persona.uid == no_link_uid)
            return false;
        }
       }
     }
-    foreach (var b_persona in b.individual.personas) {
+    foreach (var b_persona in b.personas) {
       foreach (var no_link_uid in dont_suggest_link.get (b_persona.uid)) {
-       foreach (var a_persona in a.individual.personas) {
+       foreach (var a_persona in a.personas) {
          if (a_persona.uid == no_link_uid)
            return false;
        }
@@ -119,16 +113,14 @@ public class Contacts.Store : GLib.Object {
     return true;
   }
 
-  public void add_no_suggest_link (Contact a, Contact b) {
-    var persona1 = a.get_personas_for_display ().to_array ()[0];
-    var persona2 = b.get_personas_for_display ().to_array ()[0];
+  public void add_no_suggest_link (Individual a, Individual b) {
+    var persona1 = Contacts.Utils.get_personas_for_display(a).to_array ()[0];
+    var persona2 = Contacts.Utils.get_personas_for_display(b).to_array ()[0];
     dont_suggest_link.set (persona1.uid, persona2.uid);
     write_dont_suggest_db ();
   }
 
   construct {
-    contacts = new Gee.ArrayList<Contact>();
-
     dont_suggest_link = new Gee.HashMultiMap<string, string> ();
     read_dont_suggest_db ();
 
@@ -190,11 +182,10 @@ public class Contacts.Store : GLib.Object {
           replacements.add (new_individual);
         } else if (old_individual != null) {
           // Removing an old individual.
-          var c = Contact.from_individual (old_individual);
-          remove (c);
+          removed (old_individual);
         } else if (new_individual != null) {
           // Adding a new individual.
-          add (new Contact (this, new_individual));
+          added (new_individual);
         }
       }
 
@@ -215,70 +206,49 @@ public class Contacts.Store : GLib.Object {
             break;
         }
 
+        /* Not sure
         var c = Contact.from_individual (old_individual);
         c.replace_individual (main_individual);
+        */
         foreach (var i in replacements) {
           if (i != main_individual) {
             // Already replaced this old_individual, i.e. we're splitting
             // old_individual. We just make this a new one.
-            add (new Contact (this, i));
+            added (i);
           }
         }
       }
     }
   }
 
-  public delegate bool ContactMatcher (Contact c);
-  public async Contact? find_contact (ContactMatcher matcher) {
-    foreach (var c in contacts) {
-      if (matcher (c))
-       return c;
-    }
-    if (is_quiescent)
-      return null;
-
-    Contact? matched = null;
-    ulong id2, id3;
-    SourceFunc callback = find_contact.callback;
-    id2 = this.added.connect ( (c) => {
-       if (matcher (c)) {
-         matched = c;
-         callback ();
-       }
-      });
-    id3 = this.quiescent.connect ( () => {
-       callback();
-      });
-    yield;
-    this.disconnect (id2);
-    this.disconnect (id3);
-    return matched;
+  public Collection<Individual> get_contacts () {
+    return aggregator.individuals.values.read_only_view;
   }
 
-  public Contact? find_contact_with_persona (Persona persona) {
-    foreach (var contact in contacts) {
-      if (contact.individual.personas.contains (persona))
-       return contact;
+  public async Individual? find_contact (Query query) {
+    // Wait that the store gets quiescent if it isn't already
+    if (!is_quiescent) {
+      ulong signal_id;
+      SourceFunc callback = find_contact.callback;
+      signal_id = this.quiescent.connect ( () => {
+        callback();
+      });
+      yield;
+      this.disconnect (signal_id);
     }
-    return null;
-  }
-
-  public Collection<Contact> get_contacts () {
-    return contacts.read_only_view;
-  }
-
-  private void add (Contact c) {
-    contacts.add (c);
-    added (c);
-  }
 
-  private void remove (Contact c) {
-    var i = contacts.index_of (c);
-    if (i != contacts.size - 1)
-      contacts.set (i, contacts.get (contacts.size - 1));
-    contacts.remove_at (contacts.size - 1);
+    Individual? matched = null;
+    // We search for the closest matching Individual
+    uint strength = 0;
+    foreach (var i in this.aggregator.individuals.values) {
+      var this_strength = query.is_match(i);
+      if (this_strength > strength) {
+        matched = i;
+        strength = this_strength;
+      }
+    }
 
-    removed (c);
+    return matched;
   }
 
 #if HAVE_TELEPATHY
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index 396aad9..ccaec8d 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -173,4 +173,400 @@ namespace Contacts.Utils {
     dialog.run();
     dialog.destroy();
   }
+
+  public bool has_main_persona (Individual individual) {
+    var result = false;
+    foreach (var p in individual.personas) {
+      result |= (p.store.is_primary_store && !persona_is_google_other (p));
+    }
+    return result;
+  }
+
+  public bool is_ignorable (Individual individual) {
+    var i = individual.personas.iterator();
+
+    // Look for single-persona individuals
+    if (i.next() && !i.has_next ()) {
+      var persona_store = i.get().store;
+
+      // Filter out pure key-file persona individuals as these are not very interesting
+      if (persona_store.type_id == "key-file")
+        return true;
+
+      // Filter out uncertain things like link-local xmpp
+      if (persona_store.type_id == "telepathy" &&
+          persona_store.trust_level == PersonaStoreTrust.NONE)
+        return true;
+    }
+
+    return false;
+  }
+
+  public bool suggest_link_to (Store store, Individual self, Individual other) {
+    if (non_linkable (self) || non_linkable (other))
+      return false;
+
+    if (!store.may_suggest_link (self, other))
+      return false;
+
+    /* Only connect main contacts with non-mainable contacts.
+       non-main contacts can link to any other */
+    return !has_main_persona (self) || !has_mainable_persona (other);
+  }
+
+  private bool has_pref (AbstractFieldDetails details) {
+    var evolution_pref = details.get_parameter_values ("x-evolution-ui-slot");
+    if (evolution_pref != null && Utils.get_first (evolution_pref) == "1")
+      return true;
+
+    foreach (var param in details.parameters["type"]) {
+      if (param.ascii_casecmp ("PREF") == 0)
+        return true;
+    }
+    return false;
+  }
+
+  private TypeSet select_typeset_from_fielddetails (AbstractFieldDetails a) {
+    if (a is EmailFieldDetails)
+      return TypeSet.email;
+    if (a is PhoneFieldDetails)
+      return TypeSet.phone;
+    return TypeSet.general;
+  }
+
+  public int compare_fields (void* _a, void* _b) {
+    var a = (AbstractFieldDetails) _a;
+    var b = (AbstractFieldDetails) _b;
+
+    // Fields with a PREF hint always go first (see VCard PREF attribute)
+    var a_has_pref = has_pref (a);
+    if (a_has_pref != has_pref (b))
+      return (a_has_pref)? -1 : 1;
+
+    // sort by field type first (e.g. "Home", "Work")
+    var type_set = select_typeset_from_fielddetails (a);
+    var result = type_set.format_type (a).ascii_casecmp (type_set.format_type (b));
+    if (result != 0)
+      return result;
+
+    // Try to compare by value if types are equal
+    var aa = a as AbstractFieldDetails<string>;
+    var bb = b as AbstractFieldDetails<string>;
+    if (aa != null && bb != null)
+      return strcmp (aa.value, bb.value);
+
+    // No heuristics to fall back to.
+    warning ("Unsupported AbstractFieldDetails value type");
+    return 0;
+  }
+
+  public Gee.List<T> sort_fields<T> (Collection<T> fields) {
+    var res = new ArrayList<T>();
+    res.add_all (fields);
+    res.sort (Contacts.Utils.compare_fields);
+    return res;
+  }
+
+  public string[] format_address (PostalAddress addr) {
+    string[] lines = {};
+
+    if (is_set (addr.street))
+      lines += addr.street;
+
+    if (is_set (addr.extension))
+      lines += addr.extension;
+
+    if (is_set (addr.locality))
+      lines += addr.locality;
+
+    if (is_set (addr.region))
+      lines += addr.region;
+
+    if (is_set (addr.postal_code))
+      lines += addr.postal_code;
+
+    if (is_set (addr.po_box))
+      lines += addr.po_box;
+
+    if (is_set (addr.country))
+      lines += addr.country;
+
+    if (is_set (addr.address_format))
+      lines += addr.address_format;
+
+    return lines;
+  }
+
+#if HAVE_TELEPATHY
+  public Tpf.Persona? find_im_persona (Individual individual, string protocol, string im_address) {
+    var iid = protocol + ":" + im_address;
+    foreach (var p in individual.personas) {
+      var tp = p as Tpf.Persona;
+      if (tp != null && tp.iid == iid) {
+        return tp;
+      }
+    }
+    return null;
+  }
+#endif
+
+  /* We claim something is "removable" if at least one persona is removable,
+  that will typically unlink the rest. */
+  public bool can_remove_personas (Individual individual) {
+    foreach (var p in individual.personas)
+      if (p.store.can_remove_personas == MaybeBool.TRUE)
+        return true;
+
+    return false;
+  }
+
+  public Gee.List<Persona> get_personas_for_display (Individual individual) {
+    CompareDataFunc<Persona> compare_persona_by_store = (a, b) => {
+      var store_a = a.store;
+      var store_b = b.store;
+
+      // In the same store, sort Google 'other' contacts last
+      if (store_a == store_b) {
+        if (!persona_is_google (a))
+          return 0;
+
+        var a_is_other = persona_is_google_other (a);
+        if (a_is_other != persona_is_google_other (b))
+          return a_is_other? 1 : -1;
+      }
+
+      // Sort primary stores before others
+      if (store_a.is_primary_store != store_b.is_primary_store)
+        return (store_a.is_primary_store)? -1 : 1;
+
+      // E-D-S stores get prioritized
+      if ((store_a.type_id == "eds") != (store_b.type_id == "eds"))
+        return (store_a.type_id == "eds")? -1 : 1;
+
+      // Normal case: use alphabetical sorting
+      return strcmp (store_a.id, store_b.id);
+    };
+
+    var persona_list = new ArrayList<Persona>();
+    foreach (var persona in individual.personas)
+      if (persona.store.type_id != "key-file")
+        persona_list.add (persona);
+
+    persona_list.sort ((owned) compare_persona_by_store);
+    return persona_list;
+  }
+
+  public Persona? find_primary_persona (Individual individual) {
+    foreach (var p in individual.personas)
+      if (p.store.is_primary_store)
+        return p;
+
+    return null;
+  }
+
+  public Persona? find_persona_from_uid (Individual individual, string uid) {
+    foreach (var p in individual.personas) {
+      if (p.uid == uid)
+        return p;
+    }
+    return null;
+  }
+
+  public string format_persona_stores (Individual individual) {
+    string stores = "";
+    bool first = true;
+    foreach (var p in individual.personas) {
+      if (!first)
+        stores += ", ";
+      stores += format_persona_store_name_for_contact (p);
+      first = false;
+    }
+    return stores;
+  }
+
+  public string format_persona_store_name (PersonaStore store) {
+    if (store.type_id == "eds") {
+      string? eds_name = lookup_esource_name_by_uid (store.id);
+      if (eds_name != null)
+        return eds_name;
+    }
+#if HAVE_TELEPATHY
+    if (store.type_id == "telepathy") {
+      var account = (store as Tpf.PersonaStore).account;
+      return Contacts.ImService.get_display_name (account.service);
+    }
+#endif
+
+    return store.display_name;
+  }
+
+  /* These are "regular" address book contacts, i.e. they contain a
+     persona that would be "main" if that persona was the primary store */
+  private bool has_mainable_persona (Individual individual) {
+    foreach (var p in individual.personas) {
+      if (p.store.type_id == "eds" &&
+          !persona_is_google_other (p))
+        return true;
+    }
+    return false;
+  }
+
+  /* We never want to suggest linking to google contacts that
+     are not My Contacts nor Profiles */
+  private bool non_linkable (Individual individual) {
+    bool all_unlinkable = true;
+
+    foreach (var p in individual.personas) {
+      if (!persona_is_google_other (p) ||
+          persona_is_google_profile (p))
+        all_unlinkable = false;
+    }
+
+    return all_unlinkable;
+  }
+
+  private bool persona_is_google (Persona persona) {
+    return persona.store.type_id == "eds" && esource_uid_is_google (persona.store.id);
+  }
+
+  /**
+   * Return true only for personas which are in a Google address book, but which
+   * are not in the user's "My Contacts" group in the address book.
+   */
+  public bool persona_is_google_other (Persona persona) {
+    if (!persona_is_google (persona))
+      return false;
+
+    var p = persona as Edsf.Persona;
+    return p != null && !p.in_google_personal_group;
+  }
+
+  public bool persona_is_google_profile (Persona persona) {
+    if (!persona_is_google_other (persona))
+      return false;
+
+    var u = persona as UrlDetails;
+    if (u != null && u.urls.size == 1) {
+      foreach (var url in u.urls) {
+        if (/https?:\/\/www.google.com\/profiles\/[0-9]+$/.match(url.value))
+          return true;
+      }
+    }
+    return false;
+  }
+
+  public string format_persona_store_name_for_contact (Persona persona) {
+    var store = persona.store;
+    if (store.type_id == "eds") {
+      if (persona_is_google_profile (persona))
+        return _("Google Circles");
+      else if (persona_is_google_other (persona))
+        return _("Google");
+
+      string? eds_name = lookup_esource_name_by_uid_for_contact (store.id);
+      if (eds_name != null)
+        return eds_name;
+    }
+#if HAVE_TELEPATHY
+    if (store.type_id == "telepathy") {
+      var account = (store as Tpf.PersonaStore).account;
+      return Contacts.ImService.get_display_name (account.service);
+    }
+#endif
+
+    return store.display_name;
+  }
+
+  /* Tries to set the property on all persons that have it writeable */
+  public async void set_individual_property (Individual individual, string property_name, Value value)
+    throws GLib.Error, PropertyError {
+      // Need to make a copy here as it could change during the yields
+      var personas_copy = individual.personas.to_array ();
+      foreach (var p in personas_copy) {
+        if (property_name in p.writeable_properties) {
+          yield set_persona_property (p, property_name, value);
+        }
+      }
+      //TODO: Add fallback if we can't write to any persona (Do we wan't to support that?)
+    }
+
+  public async void set_persona_property (Persona persona,
+                                          string property_name, Value new_value) throws PropertyError, 
IndividualAggregatorError {
+    /* FIXME: It should be possible to move these all to being delegates which are
+     * passed to the functions which currently call this one; but only once bgo#604827 is fixed. */
+    switch (property_name) {
+      case "alias":
+        yield (persona as AliasDetails).change_alias ((string) new_value);
+        break;
+      case "avatar":
+        yield (persona as AvatarDetails).change_avatar ((LoadableIcon?) new_value);
+        break;
+      case "birthday":
+        yield (persona as BirthdayDetails).change_birthday ((DateTime?) new_value);
+        break;
+      case "calendar-event-id":
+        yield (persona as BirthdayDetails).change_calendar_event_id ((string?) new_value);
+        break;
+      case "email-addresses":
+        yield (persona as EmailDetails).change_email_addresses ((Set<EmailFieldDetails>) new_value);
+        break;
+      case "is-favourite":
+        yield (persona as FavouriteDetails).change_is_favourite ((bool) new_value);
+        break;
+      case "gender":
+        yield (persona as GenderDetails).change_gender ((Gender) new_value);
+        break;
+      case "groups":
+        yield (persona as GroupDetails).change_groups ((Set<string>) new_value);
+        break;
+      case "im-addresses":
+        yield (persona as ImDetails).change_im_addresses ((MultiMap<string, ImFieldDetails>) new_value);
+        break;
+      case "local-ids":
+        yield (persona as LocalIdDetails).change_local_ids ((Set<string>) new_value);
+        break;
+      case "structured-name":
+        yield (persona as NameDetails).change_structured_name ((StructuredName?) new_value);
+        break;
+      case "full-name":
+        yield (persona as NameDetails).change_full_name ((string) new_value);
+        break;
+      case "nickname":
+        yield (persona as NameDetails).change_nickname ((string) new_value);
+        break;
+      case "notes":
+        yield (persona as NoteDetails).change_notes ((Set<NoteFieldDetails>) new_value);
+        break;
+      case "phone-numbers":
+        yield (persona as PhoneDetails).change_phone_numbers ((Set<PhoneFieldDetails>) new_value);
+        break;
+      case "postal-addresses":
+        yield (persona as PostalAddressDetails).change_postal_addresses ((Set<PostalAddressFieldDetails>) 
new_value);
+        break;
+      case "roles":
+        yield (persona as RoleDetails).change_roles ((Set<RoleFieldDetails>) new_value);
+        break;
+      case "urls":
+        yield (persona as UrlDetails).change_urls ((Set<UrlFieldDetails>) new_value);
+        break;
+      case "web-service-addresses":
+        yield (persona as WebServiceDetails).change_web_service_addresses ((MultiMap<string, 
WebServiceFieldDetails>) new_value);
+        break;
+      default:
+        critical ("Unknown property '%s' in Contact.set_persona_property().", property_name);
+        break;
+    }
+  }
+
+#if HAVE_TELEPATHY
+  public void fetch_contact_info (Individual individual) {
+    /* TODO: Ideally Folks should have API for this (#675131) */
+    foreach (var p in individual.personas) {
+      var tp = p as Tpf.Persona;
+      if (tp != null) {
+        tp.contact.request_contact_info_async.begin (null);
+      }
+    }
+  }
+#endif
 }
diff --git a/src/contacts-window.vala b/src/contacts-window.vala
index bcd632f..e427a32 100644
--- a/src/contacts-window.vala
+++ b/src/contacts-window.vala
@@ -163,8 +163,9 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     this.contact_pane = new ContactPane (this, this.store);
     this.contact_pane.visible = true;
     this.contact_pane.hexpand = true;
-    this.contact_pane.will_delete.connect ( (contact) => {
-        delete_contacts (new ArrayList<Contact>.wrap ({ contact }));
+    this.contact_pane.will_delete.connect ( (individual) => {
+        this.list_pane.hide_contact (individual);
+        delete_contacts (new ArrayList<Individual>.wrap ({ individual }));
      });
     this.contact_pane.contacts_linked.connect (contact_pane_contacts_linked_cb);
     this.contact_pane.display_name_changed.connect ((display_name) => {
@@ -195,8 +196,8 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     list_pane.show ();
     list_pane_stack.visible_child = list_pane;
 
-    if (this.contact_pane.contact != null)
-      list_pane.select_contact (this.contact_pane.contact);
+    if (this.contact_pane.individual != null)
+      list_pane.select_contact (this.contact_pane.individual);
 
   }
 
@@ -240,12 +241,12 @@ public class Contacts.Window : Gtk.ApplicationWindow {
 
   [GtkCallback]
   private void on_edit_button_clicked () {
-    if (this.contact_pane.contact == null)
+    if (this.contact_pane.individual == null)
       return;
 
     this.state = UiState.UPDATING;
 
-    var name = this.contact_pane.contact.individual.display_name;
+    var name = this.contact_pane.individual.display_name;
     this.right_header.title = _("Editing %s").printf (name);
     this.contact_pane.start_editing ();
   }
@@ -256,8 +257,8 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     if (this.ignore_favorite_button_toggled)
       return;
 
-    var is_fav = this.contact_pane.contact.individual.is_favourite;
-    this.contact_pane.contact.individual.is_favourite = !is_fav;
+    var is_fav = this.contact_pane.individual.is_favourite;
+    this.contact_pane.individual.is_favourite = !is_fav;
   }
 
   private void stop_editing (bool drop_changes = false) {
@@ -276,8 +277,8 @@ public class Contacts.Window : Gtk.ApplicationWindow {
       this.state = UiState.SHOWING;
     }
 
-    if (this.contact_pane.contact != null) {
-      this.right_header.title = this.contact_pane.contact.individual.display_name;
+    if (this.contact_pane.individual != null) {
+      this.right_header.title = this.contact_pane.individual.display_name;
     } else {
       this.right_header.title = "";
     }
@@ -288,23 +289,23 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     notification.show ();
   }
 
-  public void set_shown_contact (Contact? c) {
+  public void set_shown_contact (Individual? i) {
     /* FIXME: ask the user to leave edit-mode and act accordingly */
     if (this.contact_pane.on_edit_mode)
       stop_editing ();
 
-    this.contact_pane.show_contact (c);
+    this.contact_pane.show_contact (i);
     if (list_pane != null)
-      list_pane.select_contact (c);
+      list_pane.select_contact (i);
 
     // clearing right_header
-    if (c != null) {
+    if (i != null) {
       this.ignore_favorite_button_toggled = true;
-      this.favorite_button.active = c.individual.is_favourite;
+      this.favorite_button.active = i.is_favourite;
       this.ignore_favorite_button_toggled = false;
-      this.favorite_button.tooltip_text = (c.individual.is_favourite)? _("Unmark as favorite")
+      this.favorite_button.tooltip_text = (i.is_favourite)? _("Unmark as favorite")
                                                                      : _("Mark as favorite");
-      this.right_header.title = c.individual.display_name;
+      this.right_header.title = i.display_name;
     }
   }
 
@@ -397,7 +398,7 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     return false;
   }
 
-  void list_pane_selection_changed_cb (Contact? new_selection) {
+  void list_pane_selection_changed_cb (Individual? new_selection) {
     set_shown_contact (new_selection);
     if (this.state != UiState.SELECTING)
       this.state = UiState.SHOWING;
@@ -406,7 +407,7 @@ public class Contacts.Window : Gtk.ApplicationWindow {
       show_contact_pane ();
   }
 
-  void list_pane_link_contacts_cb (LinkedList<Contact> contact_list) {
+  void list_pane_link_contacts_cb (LinkedList<Individual> contact_list) {
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
@@ -432,16 +433,16 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     add_notification (notification);
   }
 
-  private void delete_contacts (Gee.List<Contact> contacts) {
+  private void delete_contacts (Gee.List<Individual> individuals) {
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
     string msg;
-    if (contacts.size == 1)
-      msg = _("Deleted contact %s").printf (contacts[0].individual.display_name);
+    if (individuals.size == 1)
+      msg = _("Deleted contact %s").printf (individuals[0].display_name);
     else
-      msg = ngettext ("%d contact deleted", "%d contacts deleted", contacts.size)
-              .printf (contacts.size);
+      msg = ngettext ("%d contact deleted", "%d contacts deleted", individuals.size)
+              .printf (individuals.size);
 
     var b = new Button.with_mnemonic (_("_Undo"));
 
@@ -458,16 +459,23 @@ public class Contacts.Window : Gtk.ApplicationWindow {
         really_delete = false;
         notification.dismiss ();
 
-        foreach (var c in contacts)
-          c.hidden = false;
+        /* Reset the contact list */
+        list_pane.undo_deletion ();
 
-        set_shown_contact (contacts[0]);
+        set_shown_contact (individuals[0]);
         this.state = UiState.SHOWING;
       });
     notification.dismissed.connect ( () => {
         if (really_delete)
-          foreach (var c in contacts)
-            c.remove_personas.begin ();
+          foreach (var i in individuals)
+            foreach (var p in i.personas) {
+              // TODO: make sure it is acctally removed
+              try {
+                p.store.remove_persona.begin (p);
+              } catch (Error e) {
+                debug ("Coudln't remove persona");
+              }
+            }
       });
 
     add_notification (notification);
diff --git a/src/meson.build b/src/meson.build
index 0401f78..b94e767 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,7 +6,6 @@ install_data('org.gnome.Contacts.gschema.xml',
 
 # Common library
 libcontacts_sources = files(
-  'contacts-contact.vala',
   'contacts-esd-setup.vala',
   'contacts-fake-persona-store.vala',
   'contacts-im-service.vala',


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