[gnome-contacts/nielsdg/gtk4] Port to GTK4 and libadwaita




commit 341e6116ffd5cea4bb1a19901bf415a8cd1ea5ab
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Thu Oct 14 01:19:13 2021 +0200

    Port to GTK4 and libadwaita

 data/contacts.gresource.xml                        |    8 +-
 data/gtk/help-overlay.ui                           |    8 -
 data/icons/birthday-symbolic.svg                   |    8 +
 data/icons/external-link-symbolic.svg              |   11 +
 data/icons/note-symbolic.svg                       |    4 +
 data/ui/contacts-accounts-list.ui                  |   15 +-
 data/ui/contacts-avatar-selector.ui                |  129 +-
 data/ui/contacts-contact-pane.ui                   |   96 +-
 data/ui/contacts-crop-cheese-dialog.ui             |  116 --
 data/ui/contacts-crop-dialog.ui                    |   82 ++
 data/ui/contacts-editor-menu.ui                    |    2 -
 data/ui/contacts-in-app-notification.ui            |   54 -
 data/ui/contacts-link-suggestion-grid.ui           |   42 +-
 data/ui/contacts-linked-personas-dialog.ui         |   16 +-
 data/ui/contacts-list-pane.ui                      |   66 +-
 data/ui/contacts-main-window.ui                    |  653 ++++-------
 data/ui/contacts-setup-window.ui                   |   92 +-
 data/ui/style.css                                  |  107 +-
 docs/meson.build                                   |    4 +-
 meson.build                                        |   14 +-
 meson_options.txt                                  |    2 +-
 po/POTFILES.in                                     |    4 +-
 po/POTFILES.skip                                   |    2 +-
 src/cc-crop-area.c                                 | 1236 ++++++++++----------
 src/contacts-accounts-list.vala                    |  100 +-
 src/contacts-addressbook-dialog.vala               |   26 +-
 src/contacts-addressbook-list.vala                 |   45 +-
 src/contacts-app.vala                              |   79 +-
 src/contacts-avatar-selector.vala                  |  230 ++--
 src/contacts-avatar.vala                           |   84 +-
 src/contacts-contact-editor.vala                   |   27 +-
 src/contacts-contact-list.vala                     |  249 ++--
 src/contacts-contact-pane.vala                     |  106 +-
 src/contacts-contact-sheet.vala                    |  394 ++++---
 ...heese-dialog.vala => contacts-crop-dialog.vala} |   66 +-
 src/contacts-delete-operation.vala                 |   55 +
 src/contacts-editor-persona.vala                   |   98 +-
 src/contacts-editor-property.vala                  |  557 ++++-----
 src/contacts-esd-setup.vala                        |   86 +-
 src/contacts-fake-persona-store.vala               |    2 +-
 src/contacts-in-app-notification.vala              |   77 --
 src/contacts-link-operation.vala                   |   79 ++
 src/contacts-link-suggestion-grid.vala             |   16 +-
 src/contacts-linked-personas-dialog.vala           |   18 +-
 src/contacts-linking.vala                          |  103 --
 src/contacts-list-pane.vala                        |   25 +-
 src/contacts-main-window.vala                      |  471 +++-----
 src/contacts-operation.vala                        |   63 +
 src/contacts-setup-window.vala                     |   12 +-
 src/contacts-type-combo.vala                       |   70 +-
 src/contacts-typeset.vala                          |  146 +--
 src/contacts-unlink-operation.vala                 |   56 +
 src/contacts-utils.vala                            |   84 +-
 src/main.vala                                      |    2 +
 src/meson.build                                    |   29 +-
 55 files changed, 2884 insertions(+), 3342 deletions(-)
---
diff --git a/data/contacts.gresource.xml b/data/contacts.gresource.xml
index 370e2ccb..11c8fd55 100644
--- a/data/contacts.gresource.xml
+++ b/data/contacts.gresource.xml
@@ -2,13 +2,17 @@
 <gresources>
   <gresource prefix="/org/gnome/Contacts">
     <file compressed="true">ui/style.css</file>
+
+    <file preprocess="xml-stripblanks">icons/birthday-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/external-link-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/note-symbolic.svg</file>
+
     <file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-accounts-list.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-avatar-selector.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-pane.ui</file>
-    <file compressed="true" preprocess="xml-stripblanks">ui/contacts-crop-cheese-dialog.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/contacts-crop-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-editor-menu.ui</file>
-    <file compressed="true" preprocess="xml-stripblanks">ui/contacts-in-app-notification.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-link-suggestion-grid.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-linked-personas-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-list-pane.ui</file>
diff --git a/data/gtk/help-overlay.ui b/data/gtk/help-overlay.ui
index 2b8a7a81..91a4057f 100644
--- a/data/gtk/help-overlay.ui
+++ b/data/gtk/help-overlay.ui
@@ -2,49 +2,41 @@
   <object class="GtkShortcutsWindow" id="help_overlay">
     <child>
       <object class="GtkShortcutsSection">
-        <property name="visible">1</property>
         <child>
           <object class="GtkShortcutsGroup">
-            <property name="visible">1</property>
             <property name="title" translatable="yes" context="shortcut window">Overview</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">F1</property>
                 <property name="title" translatable="yes" context="shortcut window">Help</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">F10</property>
                 <property name="title" translatable="yes" context="shortcut window">Open menu</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Primary&gt;n</property>
                 <property name="title" translatable="yes" context="shortcut window">Create a new 
contact</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Primary&gt;f &lt;Primary&gt;s</property>
                 <property name="title" translatable="yes" context="shortcut window">Search</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Primary&gt;question</property>
                 <property name="title" translatable="yes" context="shortcut window">Shortcut list</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Primary&gt;q</property>
                 <property name="title" translatable="yes" context="shortcut window">Quit</property>
               </object>
diff --git a/data/icons/birthday-symbolic.svg b/data/icons/birthday-symbolic.svg
new file mode 100644
index 00000000..2924f38b
--- /dev/null
+++ b/data/icons/birthday-symbolic.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg";>
+    <g fill="#2e3436">
+        <path d="m 1 9 v 2 l 3.5 1 l 3.5 -1 l 3.5 1 l 3.5 -1 v -2 c 0 -2 -2 -2 -2 -2 h -10 s -2 0 -2 2 z m 7 
3 l -3.5 1 l -3.5 -1 v 4 h 14 v -4 l -3.5 1 z m 0 0"/>
+        <path d="m 7 4 h 2 v 4 h -2 z m 0 0"/>
+        <path d="m 8 0.507812 s -1.058594 0.898438 -0.996094 1.496094 c 0.042969 0.464844 0.527344 0.996094 
0.996094 0.996094 s 0.953125 -0.53125 0.996094 -0.996094 c 0.0625 -0.597656 -0.996094 -1.496094 -0.996094 
-1.496094 z m 0 0"/>
+    </g>
+</svg>
diff --git a/data/icons/external-link-symbolic.svg b/data/icons/external-link-symbolic.svg
new file mode 100644
index 00000000..1ac7ee70
--- /dev/null
+++ b/data/icons/external-link-symbolic.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg";>
+    <g fill="#2e3436">
+        <path d="m 2 3 v 11 h 11 v -4 h -2 v 2 h -7 v -7 h 2 v -2 z m 0 0"/>
+        <path d="m 9 2 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 3 v 3 c 0 0.550781 0.449219 1 1 1 s 
1 -0.449219 1 -1 v -4 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 0"/>
+        <path d="m 13 2 h 1 v 1 h -1 z m 0 0"/>
+        <path d="m 12.292969 2.289062 l -4.5 4.46875 c -0.390625 0.390626 -0.390625 1.027344 0 1.414063 c 
0.390625 0.394531 1.023437 0.394531 1.414062 0.007813 l 4.5 -4.46875 c 0.390625 -0.390626 0.390625 -1.027344 
0 -1.414063 c -0.386719 -0.394531 -1.019531 -0.394531 -1.414062 -0.007813 z m 0 0"/>
+        <path d="m 13 7 h 1 v 1 h -1 z m 0 0"/>
+        <path d="m 8 2 h 1 v 1 h -1 z m 0 0"/>
+    </g>
+</svg>
diff --git a/data/icons/note-symbolic.svg b/data/icons/note-symbolic.svg
new file mode 100644
index 00000000..ca8c9497
--- /dev/null
+++ b/data/icons/note-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg";>
+    <path d="m 3.121094 2 c -1.175782 0 -2.121094 0.945312 -2.121094 2.121094 v 6.757812 c 0 1.175782 
0.945312 2.121094 2.121094 2.121094 h 5.878906 l 4 3 v -3.011719 c 1.117188 -0.0625 2 -0.976562 2 -2.109375 v 
-6.757812 c 0 -1.175782 -0.945312 -2.121094 -2.121094 -2.121094 z m -0.121094 3 h 10 v 1 h -10 z m 0 2 h 10 v 
1 h -10 z m 0 2 h 10 v 1 h -10 z m 0 0" fill="#2e3436"/>
+</svg>
diff --git a/data/ui/contacts-accounts-list.ui b/data/ui/contacts-accounts-list.ui
index d08d297e..b95ce630 100644
--- a/data/ui/contacts-accounts-list.ui
+++ b/data/ui/contacts-accounts-list.ui
@@ -1,11 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.22 -->
-  <template class="ContactsAccountsList" parent="GtkListBox">
-    <property name="visible">True</property>
-    <property name="selection_mode">none</property>
+  <template class="ContactsAccountsList" parent="AdwBin">
     <style>
-      <class name="content"/>
+      <class name="contacts-accounts-list"/>
     </style>
+    <child>
+      <object class="GtkListBox" id="listbox">
+        <property name="selection_mode">none</property>
+        <style>
+          <class name="boxed-list"/>
+        </style>
+      </object>
+    </child>
   </template>
 </interface>
diff --git a/data/ui/contacts-avatar-selector.ui b/data/ui/contacts-avatar-selector.ui
index 336b6baf..1cb35a3e 100644
--- a/data/ui/contacts-avatar-selector.ui
+++ b/data/ui/contacts-avatar-selector.ui
@@ -1,124 +1,91 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
-  <template class="ContactsAvatarSelector" parent="GtkWindow">
-    <property name="can_focus">False</property>
+  <template class="ContactsAvatarSelector" parent="GtkDialog">
     <property name="modal">True</property>
     <property name="default_width">400</property>
     <property name="default_height">400</property>
-    <property name="destroy_with_parent">True</property>
-    <property name="skip_taskbar_hint">True</property>
-    <signal name="delete-event" handler="on_delete_event" swapped="no"/>
-    <child type="titlebar">
-      <object class="GtkHeaderBar">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="can_focus">False</property>
-        <child>
-          <object class="GtkButton">
-            <property name="label" translatable="yes">Cancel</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
-          </object>
-        </child>
-        <child>
-          <object class="GtkButton">
-            <property name="label" translatable="yes">Done</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <signal name="clicked" handler="on_done_clicked" swapped="no"/>
-            <style>
-              <class name="suggested-action"/>
-            </style>
-          </object>
-          <packing>
-            <property name="pack_type">end</property>
-          </packing>
-        </child>
+    <property name="title" translatable="yes">Select a new avatar</property>
+
+    <child type="action">
+      <object class="GtkButton" id="cancel_button">
+        <property name="label" translatable="yes">Cancel</property>
+        <property name="receives_default">True</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="done_button">
+        <property name="label" translatable="yes">Done</property>
+        <style>
+          <class name="suggested-action"/>
+        </style>
       </object>
     </child>
-    <child>
+    <action-widgets>
+      <action-widget response="cancel">cancel_button</action-widget>
+      <action-widget response="ok" default="true">done_button</action-widget>
+    </action-widgets>
+
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkScrolledWindow">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="hscrollbar_policy">never</property>
             <property name="hexpand">True</property>
             <property name="vexpand">True</property>
             <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_left">10</property>
-                <property name="margin_right">10</property>
-                <property name="margin_top">10</property>
-                <property name="margin_bottom">10</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">10</property>
+              <object class="GtkViewport">
+                <property name="scroll-to-focus">True</property>
                 <child>
-                  <object class="GtkFlowBox" id="thumbnail_grid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="column_spacing">5</property>
-                    <property name="row_spacing">5</property>
-                    <property name="max_children_per_line">8</property>
-                    <property name="selection_mode">single</property>
-                    <property name="homogeneous">False</property>
+                  <object class="GtkBox">
+                    <property name="margin-start">10</property>
+                    <property name="margin-end">10</property>
+                    <property name="margin-top">10</property>
+                    <property name="margin-bottom">10</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">10</property>
+                    <child>
+                      <object class="GtkFlowBox" id="thumbnail_grid">
+                        <property name="column_spacing">5</property>
+                        <property name="row_spacing">5</property>
+                        <property name="max_children_per_line">8</property>
+                        <property name="selection_mode">single</property>
+                        <property name="homogeneous">False</property>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkSeparator">
-            <property name="visible">True</property>
-          </object>>
+          <object class="GtkSeparator"/>
         </child>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="margin_left">10</property>
-            <property name="margin_right">10</property>
-            <property name="margin_top">10</property>
-            <property name="margin_bottom">10</property>
+            <property name="margin-start">10</property>
+            <property name="margin-end">10</property>
+            <property name="margin-top">10</property>
+            <property name="margin-bottom">10</property>
             <property name="spacing">10</property>
             <property name="halign">center</property>
             <child>
-              <object class="GtkButton" id="cheese_button">
+              <object class="GtkButton" id="camera_button">
                 <property name="label" translatable="yes">Take a Picture…</property>
-                <property name="can_focus">True</property>
-                <property name="no_show_all">True</property>
                 <property name="receives_default">True</property>
-                <signal name="clicked" handler="on_cheese_clicked" swapped="no"/>
+                <signal name="clicked" handler="on_camera_button_clicked"/>
               </object>
             </child>
             <child>
               <object class="GtkButton">
                 <property name="label" translatable="yes">Select a File…</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
-                <signal name="clicked" handler="on_file_clicked" swapped="no"/>
+                <signal name="clicked" handler="on_file_clicked"/>
               </object>
             </child>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/data/ui/contacts-contact-pane.ui b/data/ui/contacts-contact-pane.ui
index b75c3be1..818d4f50 100644
--- a/data/ui/contacts-contact-pane.ui
+++ b/data/ui/contacts-contact-pane.ui
@@ -1,80 +1,68 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <template class="ContactsContactPane" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="ContactsContactPane" parent="AdwBin">
     <property name="hexpand">True</property>
     <property name="vexpand">True</property>
     <child>
       <object class="GtkStack" id="stack">
-        <property name="visible">True</property>
-        <property name="visible-child">none_selected_page</property>
         <child>
-          <object class="HdyStatusPage" id="none_selected_page">
-            <property name="visible">True</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="icon_name">avatar-default-symbolic</property>
-            <property name="title" translatable="yes">Select a Contact</property>
-          </object>
-          <packing>
+          <object class="GtkStackPage" id="none_selected_page">
             <property name="name">none-selected-page</property>
-          </packing>
+            <property name="child">
+              <object class="AdwStatusPage">
+                <property name="icon_name">avatar-default-symbolic</property>
+                <property name="title" translatable="yes">Select a Contact</property>
+              </object>
+            </property>
+          </object>
         </child>
         <child>
-          <object class="GtkScrolledWindow" id="contact_sheet_view">
-            <property name="visible">True</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="shadow_type">none</property>
-            <property name="hscrollbar_policy">never</property>
-            <property name="vscrollbar_policy">automatic</property>
-            <child>
-              <object class="HdyClamp">
-                <property name="visible">True</property>
-                <property name="margin-top">32</property>
-                <property name="margin-bottom">32</property>
-                <property name="margin-left">24</property>
-                <property name="margin-right">24</property>
+          <object class="GtkStackPage" id="contact_sheet_page">
+            <property name="name">contact-sheet-page</property>
+            <property name="child">
+              <object class="GtkScrolledWindow" id="contact_sheet_view">
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="hscrollbar_policy">never</property>
+                <property name="vscrollbar_policy">automatic</property>
                 <child>
-                  <object class="GtkBox" id="contact_sheet_page">
-                    <property name="visible">True</property>
+                  <object class="AdwClamp" id="contact_sheet_clamp">
+                    <property name="maximum-size">500</property>
+                    <style>
+                      <class name="contacts-sheet-container"/>
+                    </style>
                   </object>
                 </child>
               </object>
-            </child>
+            </property>
           </object>
-          <packing>
-            <property name="name">contact-sheet-page</property>
-          </packing>
         </child>
         <child>
-          <object class="GtkScrolledWindow" id="contact_editor_view">
-            <property name="visible">True</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="shadow_type">none</property>
-            <property name="hscrollbar_policy">never</property>
-            <property name="vscrollbar_policy">automatic</property>
-            <child>
-              <object class="HdyClamp">
-                <property name="visible">True</property>
-                <property name="margin-top">32</property>
-                <property name="margin-bottom">32</property>
-                <property name="margin-left">24</property>
-                <property name="margin-right">24</property>
+          <object class="GtkStackPage" id="contact_editor_page">
+            <property name="name">contact-editor-page</property>
+            <property name="child">
+              <object class="GtkScrolledWindow" id="contact_editor_view">
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="hscrollbar_policy">never</property>
+                <property name="vscrollbar_policy">automatic</property>
                 <child>
-                  <object class="GtkBox" id="contact_editor_page">
-                    <property name="visible">True</property>
+                  <object class="AdwClamp" id="contact_editor_clamp">
+                    <style>
+                      <class name="contacts-contact-editor-container"/>
+                    </style>
+                    <property name="maximum-size" bind-source="contact_sheet_clamp" 
bind-property="maximum-size" bind-flags="sync-create"/>
+                    <child>
+                      <object class="GtkBox" id="contact_editor_box">
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
-            </child>
+            </property>
           </object>
-          <packing>
-            <property name="name">contact-editor-page</property>
-          </packing>
         </child>
+        <property name="visible-child-name">none-selected-page</property>
       </object>
     </child>
   </template>
diff --git a/data/ui/contacts-crop-dialog.ui b/data/ui/contacts-crop-dialog.ui
new file mode 100644
index 00000000..e5e127f9
--- /dev/null
+++ b/data/ui/contacts-crop-dialog.ui
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContactsCropDialog" parent="GtkWindow">
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">400</property>
+    <property name="destroy_with_parent">True</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="show-title-buttons">False</property>
+        <child>
+          <object class="GtkButton">
+            <property name="label" translatable="yes">Cancel</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="headerbar_stack">
+            <!-- <property name="visible-child-name" bind-source="stack" bind-property="visible-child-name" 
/> XXX -->
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">crop</property>
+                <property name="child">
+                  <object class="GtkBox">
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkButton" id="take_another_button">
+                        <property name="label" translatable="yes">Take Another…</property>
+                        <property name="receives_default">True</property>
+                        <signal name="clicked" handler="on_take_another_clicked" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="label" translatable="yes">Done</property>
+                        <property name="receives_default">True</property>
+                        <signal name="clicked" handler="on_done_clicked" swapped="no"/>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">cheese</property>
+                <property name="child">
+                  <object class="GtkButton">
+                    <property name="receives_default">True</property>
+                    <property name="halign">end</property>
+                    <signal name="clicked" handler="on_take_pic_clicked" swapped="no"/>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon_name">camera-photo-symbolic</property>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkStack" id="stack">
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/contacts-editor-menu.ui b/data/ui/contacts-editor-menu.ui
index 21ac6bb2..92eb32a3 100644
--- a/data/ui/contacts-editor-menu.ui
+++ b/data/ui/contacts-editor-menu.ui
@@ -3,12 +3,10 @@
   <object class="GtkPopoverMenu" id="editor_menu">
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <property name="margin">10</property>
         <child>
           <object class="GtkModelButton">
-            <property name="visible">True</property>
             <property name="action-name">persona.change-addressbook</property>
             <property name="text" translatable="yes">Change Addressbook</property>
           </object>
diff --git a/data/ui/contacts-link-suggestion-grid.ui b/data/ui/contacts-link-suggestion-grid.ui
index 30511c78..8b5e34e8 100644
--- a/data/ui/contacts-link-suggestion-grid.ui
+++ b/data/ui/contacts-link-suggestion-grid.ui
@@ -1,26 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.22 -->
   <template class="ContactsLinkSuggestionGrid" parent="GtkGrid">
     <property name="orientation">horizontal</property>
-    <property name="visible">True</property>
-    <property name="valign">end</property>
     <property name="column_spacing">6</property>
+    <style>
+      <class name="contacts-link-suggestion"/>
+    </style>
     <child>
       <object class="GtkLabel" id="description_label">
-        <property name="visible">True</property>
         <property name="valign">end</property>
         <property name="halign">start</property>
         <property name="hexpand">True</property>
-        <property name="margin_top">12</property>
+        <property name="margin-top">12</property>
         <property name="wrap">True</property>
         <property name="wrap_mode">word-char</property>
+        <layout>
+          <property name="row">0</property>
+          <property name="column">1</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">1</property>
-        <property name="top_attach">0</property>
-        <property name="height">1</property>
-      </packing>
     </child>
     <child>
       <object class="GtkLabel" id="extra_info_label">
@@ -32,16 +30,14 @@
         <style>
           <class name="dim-label"/>
         </style>
+        <layout>
+          <property name="row">1</property>
+          <property name="column">1</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">1</property>
-        <property name="top_attach">1</property>
-        <property name="height">1</property>
-      </packing>
     </child>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="valign">center</property>
         <property name="orientation">horizontal</property>
         <property name="spacing">6</property>
@@ -51,7 +47,6 @@
         <property name="margin_end">6</property>
         <child>
           <object class="GtkButton" id="accept_button">
-            <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="label" translatable="yes">Link Contacts</property>
             <property name="margin_end">6</property>
@@ -59,31 +54,28 @@
         </child>
         <child>
           <object class="GtkSeparator">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
           </object>
         </child>
         <child>
           <object class="GtkButton" id="reject_button">
-            <property name="visible">True</property>
             <property name="valign">center</property>
             <style>
               <class name="flat"/>
             </style>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
                 <property name="icon_name">window-close-symbolic</property>
               </object>
             </child>
           </object>
         </child>
+        <layout>
+          <property name="row">0</property>
+          <property name="column">2</property>
+          <property name="row-span">2</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">2</property>
-        <property name="top_attach">0</property>
-        <property name="height">2</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/contacts-linked-personas-dialog.ui b/data/ui/contacts-linked-personas-dialog.ui
index 16e46105..f2ee8c52 100644
--- a/data/ui/contacts-linked-personas-dialog.ui
+++ b/data/ui/contacts-linked-personas-dialog.ui
@@ -2,45 +2,43 @@
 <interface>
   <requires lib="gtk+" version="3.22"/>
   <template class="ContactsLinkedPersonasDialog" parent="GtkDialog">
-    <property name="visible">True</property>
     <property name="modal">True</property>
     <property name="default_width">600</property>
     <property name="default_height">400</property>
     <child type="titlebar">
       <object class="GtkHeaderBar">
-        <property name="visible">True</property>
         <property name="subtitle" translatable="yes">Linked Accounts</property>
       </object>
     </child>
     <child internal-child="vbox">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkGrid">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <property name="column_spacing">8</property>
             <property name="row_spacing">12</property>
             <child>
               <object class="GtkScrolledWindow">
-                <property name="visible">True</property>
                 <property name="hscrollbar_policy">never</property>
                 <property name="vscrollbar_policy">automatic</property>
                 <property name="hexpand">True</property>
                 <property name="vexpand">True</property>
                 <property name="shadow_type">in</property>
                 <child>
-                  <object class="GtkListBox" id="linked_accounts_view">
-                    <property name="visible">True</property>
-                    <property name="selection_mode">none</property>
+                  <object class="GtkViewport">
+                    <property name="scroll-to-focus">True</property>
+                    <child>
+                      <object class="GtkListBox" id="linked_accounts_view">
+                        <property name="selection_mode">none</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
             </child>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
                 <property name="halign">center</property>
                 <property name="label" translatable="yes">You can link contacts by selecting them from the 
contacts list</property>
               </object>
diff --git a/data/ui/contacts-list-pane.ui b/data/ui/contacts-list-pane.ui
index 00ec107b..450cf5f5 100644
--- a/data/ui/contacts-list-pane.ui
+++ b/data/ui/contacts-list-pane.ui
@@ -1,89 +1,53 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.15.2 on Thu Aug 15 15:33:02 2013 -->
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
-  <template class="ContactsListPane" parent="GtkFrame">
-    <property name="can_focus">False</property>
-    <property name="hexpand">False</property>
-    <property name="hexpand_set">True</property>
-    <property name="shadow_type">none</property>
+  <template class="ContactsListPane" parent="AdwBin">
     <child>
-      <object class="GtkGrid">
+      <object class="GtkBox">
         <property name="orientation">vertical</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkSearchEntry" id="filter_entry">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="primary_icon_name">edit-find-symbolic</property>
-            <property name="primary_icon_activatable">False</property>
-            <property name="primary_icon_sensitive">False</property>
-            <property name="margin">6</property>
-            <property name="placeholder_text" translatable="yes">Type to search</property>
-            <signal name="search_changed" handler="filter_entry_changed" object="ContactsListPane" 
after="no" swapped="no"/>
+            <property name="placeholder-text" translatable="yes">Type to search</property>
+            <signal name="search-changed" handler="filter_entry_changed"/>
+            <style>
+              <class name="contacts-filter-entry"/>
+            </style>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
         </child>
         <child>
-          <object class="GtkScrolledWindow" id="contacts_list_container">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+          <object class="GtkScrolledWindow">
             <property name="hexpand">True</property>
             <property name="vexpand">True</property>
             <property name="hscrollbar_policy">never</property>
-            <property name="no_show_all">True</property>
+            <child>
+              <object class="GtkViewport" id="contacts_list_container">
+                <property name="scroll-to-focus">True</property>
+              </object>
+            </child>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">1</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkActionBar" id="actions_bar">
-            <property name="visible">False</property>
+            <property name="revealed">False</property>
             <child>
               <object class="GtkButton" id="link_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="label" translatable="yes" comments="Link refers to the verb, from linking 
contacts together">Link</property>
-                <property name="width_request">70</property>
                 <property name="sensitive">False</property>
                 <signal name="clicked" handler="on_link_button_clicked"/>
               </object>
-              <packing>
-                <property name="pack_type">start</property>
-              </packing>
             </child>
-            <child>
+            <child type="end">
               <object class="GtkButton" id="delete_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="label" translatable="yes">Remove</property>
-                <property name="width_request">70</property>
                 <property name="sensitive">False</property>
                 <signal name="clicked" handler="on_delete_button_clicked"/>
                 <style>
                   <class name="destructive-action"/>
                 </style>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">2</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/data/ui/contacts-main-window.ui b/data/ui/contacts-main-window.ui
index ee0cfd1f..78cfd7b6 100644
--- a/data/ui/contacts-main-window.ui
+++ b/data/ui/contacts-main-window.ui
@@ -1,492 +1,261 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.22 -->
-  <object class="GtkPopoverMenu" id="hamburger_menu_popover">
-    <child>
-      <object class="GtkBox" id="hamburger_menu_box">
-        <property name="visible">True</property>
-        <property name="margin">12</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="margin">5</property>
-            <property name="halign">start</property>
-            <property name="label" translatable="yes">List Contacts By:</property>
-            <attributes>
-              <attribute name="weight" value="bold"/>
-              <attribute name="scale" value="0.8"/>
-            </attributes>
-            <style>
-              <class name="dim-label"/>
-            </style>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="sort_on_firstname_button">
-            <property name="visible">True</property>
-            <property name="text" translatable="yes">First Name</property>
-            <property name="role">radio</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="sort_on_surname_button">
-            <property name="visible">True</property>
-            <property name="text" translatable="yes">Surname</property>
-            <property name="role">radio</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSeparator">
-            <property name="margin-top">6</property>
-            <property name="margin-bottom">6</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="can_focus">True</property>
-            <property name="text" translatable="yes">Change Address Book…</property>
-            <property name="action-name">app.change-book</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="can_focus">True</property>
-            <property name="text" translatable="yes">Online Accounts &lt;sup&gt;↗&lt;/sup&gt;</property>
-            <property name="action-name">app.online-accounts</property>
-            <property name="use-markup">True</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSeparator">
-            <property name="margin-top">6</property>
-            <property name="margin-bottom">6</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="can_focus">True</property>
-            <property name="text" translatable="yes">Keyboard Shortcuts</property>
-            <property name="action-name">win.show-help-overlay</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="can_focus">True</property>
-            <property name="text" translatable="yes">Help</property>
-            <property name="action-name">app.help</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="can_focus">True</property>
-            <property name="text" translatable="yes">About Contacts</property>
-            <property name="action-name">app.about</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-  <object class="GtkPopoverMenu" id="contact_sheet_menu">
+  <menu id="hamburger_menu_popover">
+    <section>
+      <attribute name="label" translatable="yes">List Contacts By:</attribute>
+      <item>
+        <attribute name="label" translatable="yes">First Name</attribute>
+        <attribute name="action">window.sort-on</attribute>
+        <attribute name="target">firstname</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Surname</attribute>
+        <attribute name="action">window.sort-on</attribute>
+        <attribute name="target">surname</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Change Address Book…</attribute>
+        <attribute name="action">app.change-book</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Online Accounts &lt;sup&gt;↗&lt;/sup&gt;</attribute>
+        <attribute name="action">app.online-accounts</attribute>
+        <attribute name="use-markup">True</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
+        <attribute name="action">win.show-help-overlay</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Help</attribute>
+        <attribute name="action">app.help</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">About Contacts</attribute>
+        <attribute name="action">app.about</attribute>
+      </item>
+    </section>
+  </menu>
+
+  <menu id="contact_sheet_menu">
+    <section>
+      <!-- XXX -->
+      <item>
+        <attribute name="action">window.unlink-contact</attribute>
+        <attribute name="label" translatable="yes">Unlink</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+      </item>
+    </section>
+  </menu>
+
+  <template class="ContactsMainWindow" parent="AdwApplicationWindow">
+    <property name="default_width">800</property>
+    <property name="default_height">600</property>
+    <property name="icon_name">gnome-contacts</property>
+
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="orientation">vertical</property>
-        <property name="margin">10</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">False</property>
-            <property name="action-name">window.share-contact</property>
-            <property name="text" translatable="yes">Share</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="action-name">window.edit-contact</property>
-            <property name="text" translatable="yes">Edit</property>
-          </object>
-        </child>
+      <object class="GtkShortcutController">
+        <property name="scope">global</property>
         <child>
-          <object class="GtkModelButton" id="unlink_button">
-            <property name="visible">True</property>
-            <property name="action-name">window.unlink-contact</property>
-            <property name="text" translatable="yes">Unlink</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSeparator">
-            <property name="visible">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="action-name">window.delete-contact</property>
-            <property name="text" translatable="yes">Delete</property>
+          <object class="GtkShortcut">
+            <property name="trigger">&lt;Control&gt;n</property>
+            <property name="action">action(window.new-contact)</property>
           </object>
         </child>
       </object>
     </child>
-  </object>
-  <template class="ContactsMainWindow" parent="HdyApplicationWindow">
-    <property name="can_focus">False</property>
-    <property name="default_width">800</property>
-    <property name="default_height">600</property>
-    <property name="icon_name">gnome-contacts</property>
-    <property name="title" translatable="yes">Contacts</property>
-    <signal name="key-press-event" handler="key_press_event_cb" object="ContactsMainWindow" after="yes" 
swapped="no"/>
-    <signal name="delete-event" handler="delete_event_cb" object="ContactsMainWindow" after="no" 
swapped="no"/>
+
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
-          <object class="HdyLeaflet" id="header">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="mode-transition-duration" bind-source="content_box" 
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
-            <property name="child-transition-duration" bind-source="content_box" 
bind-property="child-transition-duration" bind-flags="bidirectional|sync-create"/>
-            <property name="transition-type" bind-source="content_box" bind-property="transition-type" 
bind-flags="bidirectional|sync-create"/>
+          <object class="AdwLeaflet" id="content_box">
+            <property name="can-navigate-back">True</property>
+            <signal name="notify::folded" handler="on_folded"/>
+            <signal name="notify::child-transition-running" handler="on_child_transition_running"/>
+
             <child>
-              <object class="HdyHeaderBar" id="left_header">
-                <property name="visible">True</property>
-                <property name="hexpand">False</property>
-                <property name="can_focus">False</property>
-                <property name="title" translatable="yes">Contacts</property>
-                <property name="show_close_button">True</property>
-                <child>
-                  <object class="GtkButton" id="add_button">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <property name="tooltip_text" translatable="yes">Create new contact</property>
-                    <signal name="clicked" handler="new_contact" object="ContactsMainWindow" after="no" 
swapped="no"/>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="add_button_atkobject">
-                        <property name="AtkObject::accessible-name" translatable="yes">Add contact</property>
-                      </object>
-                    </child>
+              <object class="AdwLeafletPage">
+                <property name="name">list-pane</property>
+                <property name="child">
+                  <object class="GtkBox" id="list_pane_page">
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">list-add-symbolic</property>
-                        <property name="icon_size">1</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack_type">start</property>
-                  </packing>
-                </child>
+                      <object class="AdwHeaderBar" id="left_header">
+                        <property name="hexpand">False</property>
+                        <property name="show-end-title-buttons" bind-source="content_box" 
bind-property="folded" bind-flags="sync-create"/>
 
-                <child>
-                  <object class="GtkButton" id="selection_button">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <property name="tooltip_text" translatable="yes">Select Items</property>
-                    <signal name="clicked" handler="on_selection_button_clicked"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">object-select-symbolic</property>
-                        <property name="icon_size">1</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
+                        <child type="start">
+                          <object class="GtkButton" id="add_button">
+                            <property name="tooltip-text" translatable="yes">Create new contact</property>
+                            <property name="icon-name">list-add-symbolic</property>
+                            <property name="action-name">window.new-contact</property>
+                          </object>
+                        </child>
 
-                <child>
-                  <object class="GtkMenuButton" id="hamburger_menu_button">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="popover">hamburger_menu_popover</property>
-                                        <accelerator key="F10" signal="clicked"/>
-                    <property name="tooltip_text" translatable="yes">Menu</property>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">open-menu-symbolic</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="select_cancel_button">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="label" translatable="yes">Cancel</property>
-                    <property name="tooltip_text" translatable="yes">Cancel selection</property>
-                  </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="name">list-pane</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator" id="header_separator">
-                <property name="visible">True</property>
-                <style>
-                  <class name="sidebar"/>
-                </style>
-              </object>
-              <packing>
-                <property name="navigatable">False</property>
-              </packing>
-            </child>
-            <child>
-              <object class="HdyHeaderBar" id="right_header">
-                <property name="visible">True</property>
-                <property name="hexpand">True</property>
-                <property name="show_close_button">True</property>
-                <child>
-                  <object class="GtkRevealer" id="back_revealer">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="transition-type">slide-right</property>
-                    <property name="transition-duration" bind-source="content_box" 
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
-                    <child>
-                      <object class="GtkButton" id="back">
-                        <property name="visible">True</property>
-                        <property name="valign">center</property>
-                        <property name="use-underline">True</property>
-                        <signal name="clicked" handler="on_back_clicked"/>
-                        <style>
-                          <class name="image-button"/>
-                        </style>
-                        <child internal-child="accessible">
-                          <object class="AtkObject" id="a11y-back">
-                            <property name="accessible-name" translatable="yes">Back</property>
+                        <child type="end">
+                          <object class="GtkMenuButton" id="hamburger_menu_button">
+                            <property name="menu-model">hamburger_menu_popover</property>
+                            <property name="primary">True</property>
+                            <property name="tooltip_text" translatable="yes">Menu</property>
+                            <property name="icon-name">open-menu-symbolic</property>
                           </object>
                         </child>
-                        <child>
-                          <object class="GtkImage" id="back_image">
-                            <property name="visible">True</property>
-                            <property name="icon-name">go-previous-symbolic</property>
-                            <property name="icon-size">1</property>
+                        <child type="end">
+                          <object class="GtkButton" id="selection_button">
+                            <property name="icon-name">selection-mode-symbolic</property>
+                            <property name="tooltip-text" translatable="yes">Select Items</property>
+                            <signal name="clicked" handler="on_selection_button_clicked"/>
+                          </object>
+                        </child>
+                        <child type="end">
+                          <object class="GtkButton" id="select_cancel_button">
+                            <property name="visible">False</property>
+                            <property name="label" translatable="yes">Cancel</property>
+                            <property name="tooltip_text" translatable="yes">Cancel Selection</property>
                           </object>
                         </child>
                       </object>
                     </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkButton" id="cancel_button">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="label" translatable="yes">_Cancel</property>
-                    <property name="use_underline">True</property>
-                    <property name="width_request">70</property>
-                    <property name="valign">center</property>
-                    <signal name="notify::visible" handler="on_cancel_visible" object="ContactsMainWindow" 
after="yes" swapped="no"/>
-                    <style>
-                      <class name="text-button"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="pack_type">start</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkToggleButton" id="favorite_button">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="valign">center</property>
-                    <signal name="toggled" handler="on_favorite_button_toggled"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">starred-symbolic</property>
-                        <property name="icon_size">1</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkMenuButton" id="contact_menu_button">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="popover">contact_sheet_menu</property>
-                    <property name="tooltip_text" translatable="yes">Main Menu</property>
                     <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">view-more-symbolic</property>
+                      <object class="GtkStack" id="list_pane_stack">
+                        <property name="hexpand">False</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="orientation">vertical</property>
+                            <property name="width_request">300</property>
+                            <property name="homogeneous">True</property>
+                            <property name="valign">center</property>
+                            <property name="vexpand">True</property>
+                            <child>
+                              <object class="GtkSpinner">
+                                <property name="spinning">True</property>
+                                <property name="valign">end</property>
+                                <property name="halign">center</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label1">
+                                <property name="valign">start</property>
+                                <property name="halign">center</property>
+                                <property name="label" translatable="yes">Loading</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="done_button">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="label" translatable="yes">Done</property>
-                    <property name="width_request">70</property>
-                    <property name="valign">center</property>
-                    <style>
-                      <class name="text-button"/>
-                    </style>
+                </property>
+              </object>
+            </child>
+
+            <child>
+              <object class="AdwLeafletPage">
+                <property name="navigatable">False</property>
+                <property name="child">
+                  <object class="GtkSeparator">
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">contact-pane</property>
-              </packing>
             </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkOverlay" id="notification_overlay">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+
             <child>
-              <object class="HdyLeaflet" id="content_box">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="can-swipe-back">True</property>
-                <signal name="notify::folded" handler="on_folded" object="ContactsMainWindow" after="yes" 
swapped="no"/>
-                <signal name="notify::child-transition-running" handler="on_child_transition_running" 
object="ContactsMainWindow" after="yes" swapped="no"/>
-                <child>
-                  <object class="GtkStack" id="list_pane_stack">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">False</property>
+              <object class="AdwLeafletPage">
+                <property name="name">contact-pane</property>
+                <property name="child">
+                  <object class="GtkBox" id="contact_pane_page">
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="orientation">vertical</property>
-                        <property name="width_request">300</property>
-                        <property name="homogeneous">True</property>
+                      <object class="AdwHeaderBar" id="right_header">
+                        <property name="hexpand">True</property>
                         <child>
-                          <object class="GtkSpinner">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="active">True</property>
-                            <property name="valign">end</property>
-                            <property name="halign">center</property>
-                            <style>
-                              <class name="contacts-watermark"/>
-                            </style>
+                          <object class="GtkRevealer" id="back_revealer">
+                            <property name="transition-type">slide-right</property>
+                            <child>
+                              <object class="GtkButton" id="back">
+                                <property name="valign">center</property>
+                                <property name="icon-name">go-previous-symbolic</property>
+                                <property name="tooltip_text">Back</property>
+                                <signal name="clicked" handler="on_back_clicked"/>
+                              </object>
+                            </child>
                           </object>
                         </child>
+                        <property name="title-widget">
+                          <object class="AdwWindowTitle">
+                            <property name="title"></property>
+                          </object>
+                        </property>
                         <child>
-                          <object class="GtkLabel" id="label1">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="valign">start</property>
-                            <property name="halign">center</property>
-                            <property name="label" translatable="yes">Loading</property>
+                          <object class="GtkButton" id="cancel_button">
+                            <property name="visible">False</property>
+                            <property name="label" translatable="yes">_Cancel</property>
+                            <property name="use_underline">True</property>
+                            <signal name="notify::visible" handler="on_cancel_visible" 
object="ContactsMainWindow" after="yes" swapped="no"/>
+                          </object>
+                        </child>
+                        <child type="end">
+                          <object class="GtkBox" id="contact_sheet_buttons">
+                            <property name="visible">False</property>
+                            <property name="orientation">horizontal</property>
+                            <property name="spacing">6</property>
+                            <property name="margin-end">6</property>
+                            <child>
+                              <object class="GtkToggleButton" id="favorite_button">
+                                <property name="icon-name">starred-symbolic</property>
+                                <signal name="toggled" handler="on_favorite_button_toggled"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="edit_contact_button">
+                                <property name="icon-name">document-edit-symbolic</property>
+                                <property name="action-name">window.edit-contact</property>
+                                <property name="tooltip-text" translatable="yes">Edit Contact</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="delete_contact_button">
+                                <property name="icon-name">user-trash-symbolic</property>
+                                <property name="action-name">window.delete-contact</property>
+                                <property name="tooltip-text" translatable="yes">Delete Contact</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child type="end">
+                          <object class="GtkButton" id="done_button">
+                            <property name="visible">False</property>
+                            <property name="use_underline">True</property>
+                            <property name="label" translatable="yes">Done</property>
+                            <property name="valign">center</property>
                             <style>
-                              <class name="contacts-watermark"/>
+                              <class name="suggested-action"/>
                             </style>
                           </object>
                         </child>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkOverlay" id="contact_pane_container">
+                        <property name="hexpand">True</property>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="name">list-pane</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <style>
-                      <class name="sidebar"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="navigatable">False</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkOverlay" id="contact_pane_container">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                  </object>
-                  <packing>
-                    <property name="name">contact-pane</property>
-                  </packing>
-                </child>
+                </property>
               </object>
             </child>
           </object>
         </child>
+        <child>
+          <object class="AdwToastOverlay" id="toast_overlay">
+          </object>
+        </child>
       </object>
     </child>
   </template>
-  <object class="GtkSizeGroup" id="left_pane_size_group">
-    <property name="mode">horizontal</property>
-    <widgets>
-      <widget name="left_header"/>
-      <widget name="list_pane_stack"/>
-    </widgets>
-  </object>
-  <object class="GtkSizeGroup" id="right_pane_size_group">
-    <property name="mode">horizontal</property>
-    <widgets>
-      <widget name="right_header"/>
-      <widget name="contact_pane_container"/>
-    </widgets>
-  </object>
-  <object class="HdyHeaderGroup">
-    <property name="decorate-all" bind-source="content_box" bind-property="folded" bind-flags="sync-create"/>
-    <headerbars>
-      <headerbar name="left_header"/>
-      <headerbar name="right_header"/>
-    </headerbars>
-  </object>
-  <object class="HdySwipeGroup" id="swipe_group">
-    <swipeables>
-      <swipeable name="header"/>
-      <swipeable name="content_box"/>
-    </swipeables>
-  </object>
 </interface>
-
diff --git a/data/ui/contacts-setup-window.ui b/data/ui/contacts-setup-window.ui
index 845c7a0f..6e50592c 100644
--- a/data/ui/contacts-setup-window.ui
+++ b/data/ui/contacts-setup-window.ui
@@ -1,107 +1,49 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.22 -->
-  <template class="ContactsSetupWindow" parent="HdyApplicationWindow">
+  <template class="ContactsSetupWindow" parent="AdwApplicationWindow">
     <property name="default_width">800</property>
     <property name="default_height">600</property>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <property name="width_request">360</property>
         <child>
-          <object class="HdyHeaderBar">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="title" translatable="yes">Contacts Setup</property>
-            <property name="show_close_button">False</property>
-            <style>
-              <class name="titlebar"/>
-            </style>
+          <object class="GtkHeaderBar">
+            <property name="show-title-buttons">False</property>
+            <property name="title-widget">
+              <object class="AdwWindowTitle">
+                <property name="title" translatable="yes">Contacts Setup</property>
+              </object>
+            </property>
             <child>
               <object class="GtkButton" id="setup_quit_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
                 <property name="label" translatable="yes">_Quit</property>
                 <property name="use-underline">True</property>
+                <property name="tooltip-text">Cancel Setup And Quit</property>
                 <property name="action_name">app.quit</property>
-                <child internal-child="accessible">
-                  <object class="AtkObject" id="setup_quit_button_atkobject">
-                    <property name="AtkObject::accessible-name" translatable="yes">Cancel setup and 
quit</property>
-                  </object>
-                </child>
               </object>
-              <packing>
-                <property name="pack_type">start</property>
-              </packing>
             </child>
-            <child>
+            <child type="end">
               <object class="GtkButton" id="setup_done_button">
-                <property name="visible">True</property>
                 <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="focus_on_click">False</property>
                 <property name="label" translatable="yes">_Done</property>
+                <property name="tooltip-text" translatable="yes">Complete setup</property>
                 <property name="use-underline">True</property>
-                <child internal-child="accessible">
-                  <object class="AtkObject" id="setup_done_button_atkobject">
-                    <property name="AtkObject::accessible-name" translatable="yes">Setup complete</property>
-                  </object>
-                </child>
                 <style>
-                  <class name="text-button"/>
                   <class name="suggested-action"/>
                 </style>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkScrolledWindow">
-            <property name="visible">True</property>
-            <property name="hscrollbar_policy">never</property>
-            <property name="propagate_natural_height">True</property>
-            <child>
-              <object class="HdyClamp">
-                <property name="visible">True</property>
-                <property name="margin_top">24</property>
-                <property name="margin_bottom">24</property>
-                <property name="margin_start">12</property>
-                <property name="margin_end">12</property>
-                <child>
-                  <object class="GtkBox" id="content">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
-                    <property name="halign">center</property>
-                    <property name="spacing">24</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">center</property>
-                        <property name="ellipsize">end</property>
-                        <property name="label" translatable="yes">Welcome</property>
-                        <style>
-                          <class name="large-title"/>
-                        </style>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="wrap">True</property>
-                        <property name="xalign">0</property>
-                        <property name="label" translatable="yes">Please select your main address book: this 
is where new contacts will be added. If you keep your contacts in an online account, you can add them using 
the online accounts settings.</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
+          <object class="AdwStatusPage">
+            <property name="title" translatable="yes">Welcome</property>
+            <property name="description" translatable="yes">Please select your main address book: this is 
where new contacts will be added. If you keep your contacts in an online account, you can add them using the 
online accounts settings.</property>
+            <property name="child">
+              <object class="AdwClamp" id="clamp">
               </object>
-            </child>
+            </property>
           </object>
         </child>
       </object>
diff --git a/data/ui/style.css b/data/ui/style.css
index 67bd2ad3..34dd6746 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -7,69 +7,90 @@
   background-color: transparent;
 }
 
-.contacts-suggestion {
-  border-top: 1px solid @borders;
-  background-color: shade(@theme_bg_color, 0.9);
-}
-
-/* Give the avatar in the ContactSheet some margin,
- * so it doesn't jump when switching to the editor. */
-.contacts-contact-sheet .contacts-avatar {
-  margin: 4px 8px;
-}
-
-.contacts-postal-entry {
- border-radius: 0 0 0 0;
- border-width: 1px 1px 0 1px;
+/* The search entry to filter the list of contacts */
+.contacts-filter-entry {
+  margin: 6px;
 }
 
-.contacts-postal-entry:nth-child(first) {
- border-radius: 4px 4px 0 0;
+/* The link suggestion widget */
+.contacts-link-suggestion {
+  border-top: 1px solid @borders;
+  background-color: shade(@theme_bg_color, 0.9);
 }
 
-.contacts-postal-entry:nth-child(last) {
- border-radius: 0 0 4px 4px;
- border-width: 1px;
-}
+  .contacts-link-suggestion avatar {
+    margin: 12px;
+  }
 
-/* The style for the background "watermark" image and text.
- * (copied from dim-label) */
-.contacts-watermark {
-  opacity: 0.55;
-  text-shadow: none;
+.contacts-sheet-container,
+.contacts-contact-editor-container {
+  margin: 32px 36px;
 }
 
 .contacts-avatar-popover .contact-display-name {
   font-size: 20px;
 }
+
 flowboxchild.circular {
   padding: 4px;
   border-radius: 9999px;
-  -gtk-outline-radius: 9999px;
 }
 
-.avatar-button {
-  border-radius: 50%;
-  padding: 0 0;
+/* Contact Sheet/Editor common */
+/* --------------------------- */
+
+.contacts-sheet-property,
+.contacts-editor-property {
+  margin: 9px 6px;
 }
 
-.contacts-flatten:not(:hover) {
-  background-color: transparent;
-  background-image: none;
-  border-color: transparent;
-  box-shadow: inset 0 1px rgba(255, 255, 255, 0), 0 1px rgba(255, 255, 255, 0);
-  text-shadow: none; -gtk-icon-shadow: none;
-  border: 1px solid rgba(205, 199, 194, 0.5);
+/* Contact Sheet-related CSS class */
+/* ------------------------------- */
+
+.contacts-sheet {
+}
+
+.contacts-sheet-header {
+  margin: 0 0 12px 0;
 }
 
-/* remove padding from ListBoxRow so that the revealer doesn't jump */
-row.editor-property-row {
-  padding: 0px;
+.contacts-sheet-property {
+}
+
+
+/* Contact Editor-related CSS classes */
+/* ---------------------------------- */
+
+/* Common class for all widgets editing a property  */
+.contacts-editor-property {
 }
 
-popover list {
-  background-color: @theme_bg_color;
+  .contacts-editor-property .contacts-property-icon,
+  .contacts-editor-property .contacts-editor-main-entry image {
+    margin: 12px;
+  }
+
+  .contacts-editor-property .contacts-editor-main-entry {
+    padding: 6px 6px 6px 0; /* left padding is for the icon */
+  }
+
+/* Class for editing postal address */
+.contacts-editor-address entry {
+  border-radius: 0;
+  border-width: 1px 1px 0 1px;
+  padding: 6px 6px;
 }
-popover list row:hover {
-  background-color: @theme_selected_fg_color
+
+  .contacts-editor-address entry:first-child {
+    border-radius: 4px 4px 0 0;
+  }
+
+  .contacts-editor-address entry:last-child {
+    border-radius: 0 0 4px 4px;
+    border-width: 1px;
+  }
+
+/* Widget to edit a birthday */
+.contacts-editor-birthday {
+  margin: 12px;
 }
diff --git a/docs/meson.build b/docs/meson.build
index 8ec997d9..8640f4d2 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -13,14 +13,14 @@ custom_target('docs',
     '--pkg=glib-2.0',
     '--pkg=gio-2.0',
     '--pkg=gio-unix-2.0',
-    '--pkg=gtk+-3.0',
+    '--pkg=gtk4',
     '--pkg=gnome-desktop-3.0',
     '--pkg=gee-0.8',
     '--pkg=goa-1.0',
     '--pkg=folks',
     '--pkg=folks-eds',
     '--pkg=libedataserverui-1.2',
-    '--pkg=libhandy-1',
+    '--pkg=libadwaita-1',
     '--pkg=custom',
     '--pkg=config',
     '--directory=@OUTDIR@',
diff --git a/meson.build b/meson.build
index c9b09be3..b51e3c18 100644
--- a/meson.build
+++ b/meson.build
@@ -47,17 +47,19 @@ gee = dependency('gee-0.8')
 gio_unix = dependency('gio-unix-2.0', version: '>=' + min_glib_version)
 glib = dependency('glib-2.0', version: '>=' + min_glib_version)
 gmodule_export = dependency('gmodule-export-2.0', version: '>=' + min_glib_version)
-gnome_desktop = dependency('gnome-desktop-3.0')
+# gnome_desktop = dependency('gnome-desktop-3.0')
 goa = dependency('goa-1.0')
-gtk = dependency('gtk+-3.0', version: '>= 3.23.1')
-libhandy = dependency('libhandy-1', version: '>= 1.1.0')
+gtk4_dep = dependency('gtk4', version: '>= 4.5')
+libadwaita_dep = dependency('libadwaita-1')
 # E-D-S
 libebook = dependency('libebook-1.2', version: '>=' + min_eds_version)
 libedataserver = dependency('libedataserver-1.2', version: '>=' + min_eds_version)
-libedataserverui = dependency('libedataserverui-1.2', version: '>=' + min_eds_version)
+# libedataserverui = dependency('libedataserverui-1.2', version: '>=' + min_eds_version)
+# Portals (needed for camera access)
+libportal_dep = dependency('libportal', version: '>= 0.5')
 # Cheese
-cheese_dep = dependency('cheese', required: get_option('cheese'))
-cheese_gtk_dep = dependency('cheese-gtk', version: '>= 3.3.91', required: get_option('cheese'))
+# cheese_dep = dependency('cheese', required: get_option('cheese'))
+# cheese_gtk_dep = dependency('cheese-gtk', version: '>= 3.3.91', required: get_option('cheese'))
 
 if get_option('telepathy')
   folks_telepathy = dependency('folks-telepathy', version: '>=' + min_folks_version)
diff --git a/meson_options.txt b/meson_options.txt
index 101161cc..c672bba5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,5 +1,5 @@
 option('profile', type: 'combo', choices: [ 'default', 'development' ], value: 'default', description: 
'Indicate whether this is a nightly build (used for CI purposes)')
-option('cheese', type: 'feature', value: 'auto', description: 'If enabled, allows creaing an avatar with the 
webcam')
+option('camera', type: 'boolean', value: true, description: 'Support creaing an avatar with the webcam using 
the XDG camera portal')
 option('telepathy', type: 'boolean', value: false, description: 'Enable Telepathy call/chat support.')
 option('manpage', type: 'boolean', value: true, description: 'Enable building man pages.')
 option('docs', type: 'boolean', value: false, description: 'Whether to build the valadoc docs.')
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2f3c9fae..b23cc38e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,7 +6,7 @@ data/gtk/help-overlay.ui
 data/ui/contacts-accounts-list.ui
 data/ui/contacts-avatar-selector.ui
 data/ui/contacts-contact-pane.ui
-data/ui/contacts-crop-cheese-dialog.ui
+data/ui/contacts-crop-dialog.ui
 data/ui/contacts-editor-menu.ui
 data/ui/contacts-linked-personas-dialog.ui
 data/ui/contacts-link-suggestion-grid.ui
@@ -23,7 +23,7 @@ src/contacts-contact-editor.vala
 src/contacts-contact-list.vala
 src/contacts-contact-pane.vala
 src/contacts-contact-sheet.vala
-src/contacts-crop-cheese-dialog.vala
+src/contacts-crop-dialog.vala
 src/contacts-editor-property.vala
 src/contacts-esd-setup.vala
 src/contacts-im-service.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 7348e0eb..26d3f803 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -8,11 +8,11 @@ src/contacts-addressbook-list.c
 src/contacts-app.c
 src/contacts-avatar.c
 src/contacts-avatar-selector.c
+src/contacts-camera-dialog.c
 src/contacts-contact-editor.c
 src/contacts-contact-list.c
 src/contacts-contact-pane.c
 src/contacts-contact-sheet.c
-src/contacts-crop-cheese-dialog.c
 src/contacts-editor-property.c
 src/contacts-esd-setup.c
 src/contacts-im-service.c
diff --git a/src/cc-crop-area.c b/src/cc-crop-area.c
index 799d55c1..0c341b3c 100644
--- a/src/cc-crop-area.c
+++ b/src/cc-crop-area.c
@@ -19,8 +19,6 @@
 
 #include "config.h"
 
-#include <stdlib.h>
-
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
@@ -28,21 +26,21 @@
 #include "cc-crop-area.h"
 
 struct _CcCropArea {
-        GtkDrawingArea parent_instance;
-
-        GdkPixbuf *browse_pixbuf;
-        GdkPixbuf *pixbuf;
-        GdkPixbuf *color_shifted;
-        gdouble scale;
-        GdkRectangle image;
-        GdkCursorType current_cursor;
-        GdkRectangle crop;
-        gint active_region;
-        gint last_press_x;
-        gint last_press_y;
-        gint base_width;
-        gint base_height;
-        gdouble aspect;
+    GtkDrawingArea parent_instance;
+
+    GdkPixbuf *browse_pixbuf;
+    GdkPixbuf *pixbuf;
+    GdkPixbuf *color_shifted;
+    double scale;
+    GdkRectangle image;
+    const char *current_cursor;
+    GdkRectangle crop;
+    int active_region;
+    int last_press_x;
+    int last_press_y;
+    int base_width;
+    int base_height;
+    double aspect;
 };
 
 G_DEFINE_TYPE (CcCropArea, cc_crop_area, GTK_TYPE_DRAWING_AREA);
@@ -51,757 +49,763 @@ static inline guchar
 shift_color_byte (guchar b,
                   int    shift)
 {
-        return CLAMP(b + shift, 0, 255);
+    return CLAMP(b + shift, 0, 255);
 }
 
 static void
 shift_colors (GdkPixbuf *pixbuf,
-              gint       red,
-              gint       green,
-              gint       blue,
-              gint       alpha)
+              int        red,
+              int        green,
+              int        blue,
+              int        alpha)
 {
-        gint x, y, offset, y_offset, rowstride, width, height;
-        guchar *pixels;
-        gint channels;
-
-        width = gdk_pixbuf_get_width (pixbuf);
-        height = gdk_pixbuf_get_height (pixbuf);
-        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
-        pixels = gdk_pixbuf_get_pixels (pixbuf);
-        channels = gdk_pixbuf_get_n_channels (pixbuf);
-
-        for (y = 0; y < height; y++) {
-                y_offset = y * rowstride;
-                for (x = 0; x < width; x++) {
-                        offset = y_offset + x * channels;
-                        if (red != 0)
-                                pixels[offset] = shift_color_byte (pixels[offset], red);
-                        if (green != 0)
-                                pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
-                        if (blue != 0)
-                                pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
-                        if (alpha != 0 && channels >= 4)
-                                pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
-                }
+    int offset, y_offset, rowstride, width, height;
+    guchar *pixels;
+    int channels;
+
+    width = gdk_pixbuf_get_width (pixbuf);
+    height = gdk_pixbuf_get_height (pixbuf);
+    rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+    pixels = gdk_pixbuf_get_pixels (pixbuf);
+    channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+    for (int y = 0; y < height; y++) {
+        y_offset = y * rowstride;
+        for (int x = 0; x < width; x++) {
+            offset = y_offset + x * channels;
+            if (red != 0)
+                pixels[offset] = shift_color_byte (pixels[offset], red);
+            if (green != 0)
+                pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
+            if (blue != 0)
+                pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
+            if (alpha != 0 && channels >= 4)
+                pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
         }
+    }
 }
 
 static void
 update_pixbufs (CcCropArea *area)
 {
-        gint width;
-        gint height;
-        GtkAllocation allocation;
-        gdouble scale;
-        gint dest_width, dest_height;
-        GtkWidget *widget;
-
-        widget = GTK_WIDGET (area);
-        gtk_widget_get_allocation (widget, &allocation);
-
-        width = gdk_pixbuf_get_width (area->browse_pixbuf);
-        height = gdk_pixbuf_get_height (area->browse_pixbuf);
-
-        scale = allocation.height / (gdouble)height;
-        if (scale * width > allocation.width)
-                scale = allocation.width / (gdouble)width;
-
-        dest_width = width * scale;
-        dest_height = height * scale;
-
-        if (area->pixbuf == NULL ||
-            gdk_pixbuf_get_width (area->pixbuf) != allocation.width ||
-            gdk_pixbuf_get_height (area->pixbuf) != allocation.height) {
-                g_clear_object (&area->pixbuf);
-                area->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
-                                                     gdk_pixbuf_get_has_alpha (area->browse_pixbuf),
-                                                     8,
-                                                     dest_width, dest_height);
-                gdk_pixbuf_fill (area->pixbuf, 0x0);
-
-                gdk_pixbuf_scale (area->browse_pixbuf,
-                                  area->pixbuf,
-                                  0, 0,
-                                  dest_width, dest_height,
-                                  0, 0,
-                                  scale, scale,
-                                  GDK_INTERP_BILINEAR);
-
-                g_clear_object (&area->color_shifted);
-                area->color_shifted = gdk_pixbuf_copy (area->pixbuf);
-                shift_colors (area->color_shifted, -100, -100, -100, 0);
-
-                if (area->scale == 0.0) {
-                        gdouble scale_to_80, scale_to_image, crop_scale;
-
-                        /* Scale the crop rectangle to 80% of the area, or less to fit the image */
-                        scale_to_80 = MIN ((gdouble)gdk_pixbuf_get_width (area->pixbuf) * 0.8 / 
area->base_width,
-                                           (gdouble)gdk_pixbuf_get_height (area->pixbuf) * 0.8 / 
area->base_height);
-                        scale_to_image = MIN ((gdouble)dest_width / area->base_width,
-                                              (gdouble)dest_height / area->base_height);
-                        crop_scale = MIN (scale_to_80, scale_to_image);
-
-                        area->crop.width = crop_scale * area->base_width / scale;
-                        area->crop.height = crop_scale * area->base_height / scale;
-                        area->crop.x = (gdk_pixbuf_get_width (area->browse_pixbuf) - area->crop.width) / 2;
-                        area->crop.y = (gdk_pixbuf_get_height (area->browse_pixbuf) - area->crop.height) / 2;
-                }
-
-                area->scale = scale;
-                area->image.x = (allocation.width - dest_width) / 2;
-                area->image.y = (allocation.height - dest_height) / 2;
-                area->image.width = dest_width;
-                area->image.height = dest_height;
+    GtkWidget *widget = GTK_WIDGET (area);
+    int width;
+    int height;
+    GtkAllocation allocation;
+    double scale;
+    int dest_width, dest_height;
+
+    gtk_widget_get_allocation (widget, &allocation);
+
+    width = gdk_pixbuf_get_width (area->browse_pixbuf);
+    height = gdk_pixbuf_get_height (area->browse_pixbuf);
+
+    scale = allocation.height / (double) height;
+    if (scale * width > allocation.width)
+        scale = allocation.width / (double) width;
+
+    dest_width = width * scale;
+    dest_height = height * scale;
+
+    if (area->pixbuf == NULL ||
+        gdk_pixbuf_get_width (area->pixbuf) != allocation.width ||
+        gdk_pixbuf_get_height (area->pixbuf) != allocation.height) {
+        g_clear_object (&area->pixbuf);
+        area->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                             gdk_pixbuf_get_has_alpha (area->browse_pixbuf),
+                             8,
+                             dest_width, dest_height);
+        gdk_pixbuf_fill (area->pixbuf, 0x0);
+
+        gdk_pixbuf_scale (area->browse_pixbuf,
+                          area->pixbuf,
+                          0, 0,
+                          dest_width, dest_height,
+                          0, 0,
+                          scale, scale,
+                          GDK_INTERP_BILINEAR);
+
+        g_clear_object (&area->color_shifted);
+        area->color_shifted = gdk_pixbuf_copy (area->pixbuf);
+        shift_colors (area->color_shifted, -100, -100, -100, 0);
+
+        if (area->scale == 0.0) {
+            double scale_to_80, scale_to_image, crop_scale;
+
+            /* Scale the crop rectangle to 80% of the area, or less to fit the image */
+            scale_to_80 = MIN ((double) gdk_pixbuf_get_width (area->pixbuf) * 0.8 / area->base_width,
+                               (double) gdk_pixbuf_get_height (area->pixbuf) * 0.8 / area->base_height);
+            scale_to_image = MIN ((double) dest_width / area->base_width,
+                                  (double) dest_height / area->base_height);
+            crop_scale = MIN (scale_to_80, scale_to_image);
+
+            area->crop.width = crop_scale * area->base_width / scale;
+            area->crop.height = crop_scale * area->base_height / scale;
+            area->crop.x = (gdk_pixbuf_get_width (area->browse_pixbuf) - area->crop.width) / 2;
+            area->crop.y = (gdk_pixbuf_get_height (area->browse_pixbuf) - area->crop.height) / 2;
         }
+
+        area->scale = scale;
+        area->image.x = (allocation.width - dest_width) / 2;
+        area->image.y = (allocation.height - dest_height) / 2;
+        area->image.width = dest_width;
+        area->image.height = dest_height;
+    }
 }
 
 static void
 crop_to_widget (CcCropArea    *area,
                 GdkRectangle  *crop)
 {
-        crop->x = area->image.x + area->crop.x * area->scale;
-        crop->y = area->image.y + area->crop.y * area->scale;
-        crop->width = area->crop.width * area->scale;
-        crop->height = area->crop.height * area->scale;
+    crop->x = area->image.x + area->crop.x * area->scale;
+    crop->y = area->image.y + area->crop.y * area->scale;
+    crop->width = area->crop.width * area->scale;
+    crop->height = area->crop.height * area->scale;
 }
 
 typedef enum {
-        OUTSIDE,
-        INSIDE,
-        TOP,
-        TOP_LEFT,
-        TOP_RIGHT,
-        BOTTOM,
-        BOTTOM_LEFT,
-        BOTTOM_RIGHT,
-        LEFT,
-        RIGHT
+    OUTSIDE,
+    INSIDE,
+    TOP,
+    TOP_LEFT,
+    TOP_RIGHT,
+    BOTTOM,
+    BOTTOM_LEFT,
+    BOTTOM_RIGHT,
+    LEFT,
+    RIGHT
 } Location;
 
-static gboolean
-cc_crop_area_draw (GtkWidget *widget,
-                   cairo_t   *cr)
+static void
+draw (GtkDrawingArea *draw_area,
+      cairo_t        *cr,
+      int             _width,  // XXX
+      int             _height, // XXX
+      gpointer        user_data)
 {
-        GdkRectangle crop;
-        gint width, height, ix, iy;
-        CcCropArea *uarea = CC_CROP_AREA (widget);
-
-        if (uarea->browse_pixbuf == NULL)
-                return FALSE;
+    CcCropArea *uarea = CC_CROP_AREA (draw_area);
+    GdkRectangle crop;
+    int width, height, ix, iy;
 
-        update_pixbufs (uarea);
+    if (uarea->browse_pixbuf == NULL)
+        return;
 
-        width = gdk_pixbuf_get_width (uarea->pixbuf);
-        height = gdk_pixbuf_get_height (uarea->pixbuf);
-        crop_to_widget (uarea, &crop);
+    update_pixbufs (uarea);
 
-        ix = uarea->image.x;
-        iy = uarea->image.y;
+    width = gdk_pixbuf_get_width (uarea->pixbuf);
+    height = gdk_pixbuf_get_height (uarea->pixbuf);
+    crop_to_widget (uarea, &crop);
 
-        gdk_cairo_set_source_pixbuf (cr, uarea->color_shifted, ix, iy);
-        cairo_rectangle (cr, ix, iy, width, height);
-        cairo_fill (cr);
+    ix = uarea->image.x;
+    iy = uarea->image.y;
 
-        gdk_cairo_set_source_pixbuf (cr, uarea->pixbuf, ix, iy);
-        cairo_arc (cr, crop.x + crop.width / 2, crop.y + crop.width / 2, crop.width / 2, 0, 2 * G_PI);
-        cairo_fill (cr);
+    gdk_cairo_set_source_pixbuf (cr, uarea->color_shifted, ix, iy);
+    cairo_rectangle (cr, ix, iy, width, height);
+    cairo_fill (cr);
 
-        // draw the four corners
-        cairo_set_source_rgb (cr, 1, 1, 1);
-        cairo_set_line_width (cr, 4.0);
+    gdk_cairo_set_source_pixbuf (cr, uarea->pixbuf, ix, iy);
+    cairo_arc (cr, crop.x + crop.width / 2, crop.y + crop.width / 2, crop.width / 2, 0, 2 * G_PI);
+    cairo_fill (cr);
 
-        // top left corner
-        cairo_move_to (cr, crop.x + 15, crop.y);
-        cairo_line_to (cr, crop.x, crop.y);
-        cairo_line_to (cr, crop.x, crop.y + 15);
+    // draw the four corners
+    cairo_set_source_rgb (cr, 1, 1, 1);
+    cairo_set_line_width (cr, 4.0);
 
-        // top right corner
-        cairo_move_to (cr, crop.x + crop.width - 15, crop.y);
-        cairo_line_to (cr, crop.x + crop.width, crop.y);
-        cairo_line_to (cr, crop.x + crop.width, crop.y + 15);
+    // top left corner
+    cairo_move_to (cr, crop.x + 15, crop.y);
+    cairo_line_to (cr, crop.x, crop.y);
+    cairo_line_to (cr, crop.x, crop.y + 15);
 
-        // bottom right corner
-        cairo_move_to (cr, crop.x + crop.width - 15, crop.y + crop.height);
-        cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height);
-        cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height - 15);
+    // top right corner
+    cairo_move_to (cr, crop.x + crop.width - 15, crop.y);
+    cairo_line_to (cr, crop.x + crop.width, crop.y);
+    cairo_line_to (cr, crop.x + crop.width, crop.y + 15);
 
-        // bottom left corner
-        cairo_move_to (cr, crop.x + 15, crop.y + crop.height);
-        cairo_line_to (cr, crop.x, crop.y + crop.height);
-        cairo_line_to (cr, crop.x, crop.y + crop.height - 15);
+    // bottom right corner
+    cairo_move_to (cr, crop.x + crop.width - 15, crop.y + crop.height);
+    cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height);
+    cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height - 15);
 
-        cairo_stroke (cr);
+    // bottom left corner
+    cairo_move_to (cr, crop.x + 15, crop.y + crop.height);
+    cairo_line_to (cr, crop.x, crop.y + crop.height);
+    cairo_line_to (cr, crop.x, crop.y + crop.height - 15);
 
-        return FALSE;
+    cairo_stroke (cr);
 }
 
 typedef enum {
-        BELOW,
-        LOWER,
-        BETWEEN,
-        UPPER,
-        ABOVE
+    BELOW,
+    LOWER,
+    BETWEEN,
+    UPPER,
+    ABOVE
 } Range;
 
 static Range
-find_range (gint x,
-            gint min,
-            gint max)
+find_range (int x,
+            int min,
+            int max)
 {
-        gint tolerance = 12;
-
-        if (x < min - tolerance)
-                return BELOW;
-        if (x <= min + tolerance)
-                return LOWER;
-        if (x < max - tolerance)
-                return BETWEEN;
-        if (x <= max + tolerance)
-                return UPPER;
-        return ABOVE;
+    int tolerance = 12;
+
+    if (x < min - tolerance)
+        return BELOW;
+    if (x <= min + tolerance)
+        return LOWER;
+    if (x < max - tolerance)
+        return BETWEEN;
+    if (x <= max + tolerance)
+        return UPPER;
+    return ABOVE;
 }
 
 static Location
 find_location (GdkRectangle *rect,
-               gint          x,
-               gint          y)
+               int           x,
+               int           y)
 {
-        Range x_range, y_range;
-        Location location[5][5] = {
-                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE },
-                { OUTSIDE, TOP_LEFT,    TOP,     TOP_RIGHT,    OUTSIDE },
-                { OUTSIDE, LEFT,        INSIDE,  RIGHT,        OUTSIDE },
-                { OUTSIDE, BOTTOM_LEFT, BOTTOM,  BOTTOM_RIGHT, OUTSIDE },
-                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE }
-        };
-
-        x_range = find_range (x, rect->x, rect->x + rect->width);
-        y_range = find_range (y, rect->y, rect->y + rect->height);
-
-        return location[y_range][x_range];
+    Range x_range, y_range;
+    Location location[5][5] = {
+        { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE },
+        { OUTSIDE, TOP_LEFT,    TOP,     TOP_RIGHT,    OUTSIDE },
+        { OUTSIDE, LEFT,        INSIDE,  RIGHT,        OUTSIDE },
+        { OUTSIDE, BOTTOM_LEFT, BOTTOM,  BOTTOM_RIGHT, OUTSIDE },
+        { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE }
+    };
+
+    x_range = find_range (x, rect->x, rect->x + rect->width);
+    y_range = find_range (y, rect->y, rect->y + rect->height);
+
+    return location[y_range][x_range];
 }
 
 static void
 update_cursor (CcCropArea *area,
-               gint           x,
-               gint           y)
+               int            x,
+               int            y)
 {
-        gint cursor_type;
-        GdkRectangle crop;
-        gint region;
-
-        region = area->active_region;
-        if (region == OUTSIDE) {
-                crop_to_widget (area, &crop);
-                region = find_location (&crop, x, y);
-        }
+    const char *cursor_type;
+    GdkRectangle crop;
+    int region;
 
-        switch (region) {
-        case OUTSIDE:
-                cursor_type = GDK_LEFT_PTR;
-                break;
-        case TOP_LEFT:
-                cursor_type = GDK_TOP_LEFT_CORNER;
-                break;
-        case TOP:
-                cursor_type = GDK_TOP_SIDE;
-                break;
-        case TOP_RIGHT:
-                cursor_type = GDK_TOP_RIGHT_CORNER;
-                break;
-        case LEFT:
-                cursor_type = GDK_LEFT_SIDE;
-                break;
-        case INSIDE:
-                cursor_type = GDK_FLEUR;
-                break;
-        case RIGHT:
-                cursor_type = GDK_RIGHT_SIDE;
-                break;
-        case BOTTOM_LEFT:
-                cursor_type = GDK_BOTTOM_LEFT_CORNER;
-                break;
-        case BOTTOM:
-                cursor_type = GDK_BOTTOM_SIDE;
-                break;
-        case BOTTOM_RIGHT:
-                cursor_type = GDK_BOTTOM_RIGHT_CORNER;
-                break;
-        default:
-                g_assert_not_reached ();
-        }
-
-        if (cursor_type != area->current_cursor) {
-                GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (area)),
-                                                                cursor_type);
-                gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
-                g_object_unref (cursor);
-                area->current_cursor = cursor_type;
+    region = area->active_region;
+    if (region == OUTSIDE) {
+        crop_to_widget (area, &crop);
+        region = find_location (&crop, x, y);
+    }
+
+    switch (region) {
+    case OUTSIDE:
+        cursor_type = "default";
+        break;
+    case TOP_LEFT:
+        cursor_type = "nw-resize";
+        break;
+    case TOP:
+        cursor_type = "n-resize";
+        break;
+    case TOP_RIGHT:
+        cursor_type = "ne-resize";
+        break;
+    case LEFT:
+        cursor_type = "w-resize";
+        break;
+    case INSIDE:
+        cursor_type = "move";
+        break;
+    case RIGHT:
+        cursor_type = "e-resize";
+        break;
+    case BOTTOM_LEFT:
+        cursor_type = "sw-resize";
+        break;
+    case BOTTOM:
+        cursor_type = "s-resize";
+        break;
+    case BOTTOM_RIGHT:
+        cursor_type = "se-resize";
+        break;
+    default:
+        g_assert_not_reached ();
+    }
+
+    if (cursor_type != area->current_cursor) {
+        GtkNative *native;
+        GdkCursor *cursor;
+
+        native = gtk_widget_get_native (GTK_WIDGET (area));
+        if (!native) {
+            g_warning ("Can't adjust cursor: no GtkNative found");
+            return;
         }
+        cursor = gdk_cursor_new_from_name (cursor_type, NULL);
+        gdk_surface_set_cursor (gtk_native_get_surface (native), cursor);
+        g_object_unref (cursor);
+        area->current_cursor = cursor_type;
+    }
 }
 
 static int
-eval_radial_line (gdouble center_x, gdouble center_y,
-                  gdouble bounds_x, gdouble bounds_y,
-                  gdouble user_x)
+eval_radial_line (double center_x, double center_y,
+                  double bounds_x, double bounds_y,
+                  double user_x)
 {
-        gdouble decision_slope;
-        gdouble decision_intercept;
+    double decision_slope;
+    double decision_intercept;
 
-        decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
-        decision_intercept = -(decision_slope * bounds_x);
+    decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
+    decision_intercept = -(decision_slope * bounds_x);
 
-        return (int) (decision_slope * user_x + decision_intercept);
+    return (int) (decision_slope * user_x + decision_intercept);
 }
 
 static gboolean
-cc_crop_area_motion_notify_event (GtkWidget      *widget,
-                                  GdkEventMotion *event)
+on_motion (GtkEventControllerMotion *controller,
+           double event_x,
+           double event_y,
+           gpointer user_data)
 {
-        CcCropArea *area = CC_CROP_AREA (widget);
-        gint x, y;
-        gint delta_x, delta_y;
-        gint width, height;
-        gint adj_width, adj_height;
-        gint pb_width, pb_height;
-        GdkRectangle damage;
-        gint left, right, top, bottom;
-        gdouble new_width, new_height;
-        gdouble center_x, center_y;
-        gint min_width, min_height;
-
-        if (area->browse_pixbuf == NULL)
-                return FALSE;
-
-        update_cursor (area, event->x, event->y);
-
-        crop_to_widget (area, &damage);
-        gtk_widget_queue_draw_area (widget,
-                                    damage.x - 4, damage.y - 4,
-                                    damage.width + 6, damage.height + 6);
-
-        pb_width = gdk_pixbuf_get_width (area->browse_pixbuf);
-        pb_height = gdk_pixbuf_get_height (area->browse_pixbuf);
-
-        x = (event->x - area->image.x) / area->scale;
-        y = (event->y - area->image.y) / area->scale;
-
-        delta_x = x - area->last_press_x;
-        delta_y = y - area->last_press_y;
-        area->last_press_x = x;
-        area->last_press_y = y;
-
-        left = area->crop.x;
-        right = area->crop.x + area->crop.width - 1;
-        top = area->crop.y;
-        bottom = area->crop.y + area->crop.height - 1;
-
-        center_x = (left + right) / 2.0;
-        center_y = (top + bottom) / 2.0;
+    CcCropArea *area = CC_CROP_AREA (user_data);
+    int x, y;
+    int delta_x, delta_y;
+    int width, height;
+    int adj_width, adj_height;
+    int pb_width, pb_height;
+    GdkRectangle damage;
+    int left, right, top, bottom;
+    double new_width, new_height;
+    double center_x, center_y;
+    int min_width, min_height;
+
+    if (area->browse_pixbuf == NULL)
+        return FALSE;
 
-        switch (area->active_region) {
-        case INSIDE:
-                width = right - left + 1;
-                height = bottom - top + 1;
-
-                left += delta_x;
-                right += delta_x;
-                top += delta_y;
-                bottom += delta_y;
-
-                if (left < 0)
-                        left = 0;
-                if (top < 0)
-                        top = 0;
-                if (right > pb_width)
-                        right = pb_width;
-                if (bottom > pb_height)
-                        bottom = pb_height;
-
-                adj_width = right - left + 1;
-                adj_height = bottom - top + 1;
-                if (adj_width != width) {
-                        if (delta_x < 0)
-                                right = left + width - 1;
-                        else
-                                left = right - width + 1;
-                }
-                if (adj_height != height) {
-                        if (delta_y < 0)
-                                bottom = top + height - 1;
-                        else
-                                top = bottom - height + 1;
-                }
-
-                break;
+    update_cursor (area, event_x, event_y);
 
-        case TOP_LEFT:
-                if (area->aspect < 0) {
-                        top = y;
-                        left = x;
-                }
-                else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
-                        top = y;
-                        new_width = (bottom - top) * area->aspect;
-                        left = right - new_width;
-                }
-                else {
-                        left = x;
-                        new_height = (right - left) / area->aspect;
-                        top = bottom - new_height;
-                }
-                break;
+    crop_to_widget (area, &damage);
+    // XXX
+    /* gtk_widget_queue_draw_area (GTK_WIDGET (area), */
+    /*                 damage.x - 4, damage.y - 4, */
+    /*                 damage.width + 6, damage.height + 6); */
 
-        case TOP:
-                top = y;
-                if (area->aspect > 0) {
-                        new_width = (bottom - top) * area->aspect;
-                        right = left + new_width;
-                }
-                break;
+    pb_width = gdk_pixbuf_get_width (area->browse_pixbuf);
+    pb_height = gdk_pixbuf_get_height (area->browse_pixbuf);
 
-        case TOP_RIGHT:
-                if (area->aspect < 0) {
-                        top = y;
-                        right = x;
-                }
-                else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
-                        top = y;
-                        new_width = (bottom - top) * area->aspect;
-                        right = left + new_width;
-                }
-                else {
-                        right = x;
-                        new_height = (right - left) / area->aspect;
-                        top = bottom - new_height;
-                }
-                break;
+    x = (event_x - area->image.x) / area->scale;
+    y = (event_y - area->image.y) / area->scale;
 
-        case LEFT:
-                left = x;
-                if (area->aspect > 0) {
-                        new_height = (right - left) / area->aspect;
-                        bottom = top + new_height;
-                }
-                break;
+    delta_x = x - area->last_press_x;
+    delta_y = y - area->last_press_y;
+    area->last_press_x = x;
+    area->last_press_y = y;
 
-        case BOTTOM_LEFT:
-                if (area->aspect < 0) {
-                        bottom = y;
-                        left = x;
-                }
-                else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
-                        left = x;
-                        new_height = (right - left) / area->aspect;
-                        bottom = top + new_height;
-                }
-                else {
-                        bottom = y;
-                        new_width = (bottom - top) * area->aspect;
-                        left = right - new_width;
-                }
-                break;
+    left = area->crop.x;
+    right = area->crop.x + area->crop.width - 1;
+    top = area->crop.y;
+    bottom = area->crop.y + area->crop.height - 1;
 
-        case RIGHT:
-                right = x;
-                if (area->aspect > 0) {
-                        new_height = (right - left) / area->aspect;
-                        bottom = top + new_height;
-                }
-                break;
+    center_x = (left + right) / 2.0;
+    center_y = (top + bottom) / 2.0;
 
-        case BOTTOM_RIGHT:
-                if (area->aspect < 0) {
-                        bottom = y;
-                        right = x;
-                }
-                else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
-                        right = x;
-                        new_height = (right - left) / area->aspect;
-                        bottom = top + new_height;
-                }
-                else {
-                        bottom = y;
-                        new_width = (bottom - top) * area->aspect;
-                        right = left + new_width;
-                }
-                break;
+    switch (area->active_region) {
+    case INSIDE:
+        width = right - left + 1;
+        height = bottom - top + 1;
 
-        case BOTTOM:
-                bottom = y;
-                if (area->aspect > 0) {
-                        new_width = (bottom - top) * area->aspect;
-                        right= left + new_width;
-                }
-                break;
-
-        default:
-                return FALSE;
+        left += delta_x;
+        right += delta_x;
+        top += delta_y;
+        bottom += delta_y;
+
+        if (left < 0)
+            left = 0;
+        if (top < 0)
+            top = 0;
+        if (right > pb_width)
+            right = pb_width;
+        if (bottom > pb_height)
+            bottom = pb_height;
+
+        adj_width = right - left + 1;
+        adj_height = bottom - top + 1;
+        if (adj_width != width) {
+            if (delta_x < 0)
+                right = left + width - 1;
+            else
+                left = right - width + 1;
+        }
+        if (adj_height != height) {
+            if (delta_y < 0)
+                bottom = top + height - 1;
+            else
+                top = bottom - height + 1;
         }
 
-        min_width = area->base_width / area->scale;
-        min_height = area->base_height / area->scale;
+        break;
 
-        width = right - left + 1;
-        height = bottom - top + 1;
+    case TOP_LEFT:
         if (area->aspect < 0) {
-                if (left < 0)
-                        left = 0;
-                if (top < 0)
-                        top = 0;
-                if (right > pb_width)
-                        right = pb_width;
-                if (bottom > pb_height)
-                        bottom = pb_height;
-
-                width = right - left + 1;
-                height = bottom - top + 1;
-
-                switch (area->active_region) {
-                case LEFT:
-                case TOP_LEFT:
-                case BOTTOM_LEFT:
-                        if (width < min_width)
-                                left = right - min_width;
-                        break;
-                case RIGHT:
-                case TOP_RIGHT:
-                case BOTTOM_RIGHT:
-                        if (width < min_width)
-                                right = left + min_width;
-                        break;
-
-                default: ;
-                }
-
-                switch (area->active_region) {
-                case TOP:
-                case TOP_LEFT:
-                case TOP_RIGHT:
-                        if (height < min_height)
-                                top = bottom - min_height;
-                        break;
-                case BOTTOM:
-                case BOTTOM_LEFT:
-                case BOTTOM_RIGHT:
-                        if (height < min_height)
-                                bottom = top + min_height;
-                        break;
-
-                default: ;
-                }
+            top = y;
+            left = x;
+        }
+        else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
+            top = y;
+            new_width = (bottom - top) * area->aspect;
+            left = right - new_width;
         }
         else {
-                if (left < 0 || top < 0 ||
-                    right > pb_width || bottom > pb_height ||
-                    width < min_width || height < min_height) {
-                        left = area->crop.x;
-                        right = area->crop.x + area->crop.width - 1;
-                        top = area->crop.y;
-                        bottom = area->crop.y + area->crop.height - 1;
-                }
+            left = x;
+            new_height = (right - left) / area->aspect;
+            top = bottom - new_height;
+        }
+        break;
+
+    case TOP:
+        top = y;
+        if (area->aspect > 0) {
+            new_width = (bottom - top) * area->aspect;
+            right = left + new_width;
+        }
+        break;
+
+    case TOP_RIGHT:
+        if (area->aspect < 0) {
+            top = y;
+            right = x;
+        }
+        else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
+            top = y;
+            new_width = (bottom - top) * area->aspect;
+            right = left + new_width;
+        }
+        else {
+            right = x;
+            new_height = (right - left) / area->aspect;
+            top = bottom - new_height;
+        }
+        break;
+
+    case LEFT:
+        left = x;
+        if (area->aspect > 0) {
+            new_height = (right - left) / area->aspect;
+            bottom = top + new_height;
+        }
+        break;
+
+    case BOTTOM_LEFT:
+        if (area->aspect < 0) {
+            bottom = y;
+            left = x;
+        }
+        else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
+            left = x;
+            new_height = (right - left) / area->aspect;
+            bottom = top + new_height;
+        }
+        else {
+            bottom = y;
+            new_width = (bottom - top) * area->aspect;
+            left = right - new_width;
+        }
+        break;
+
+    case RIGHT:
+        right = x;
+        if (area->aspect > 0) {
+            new_height = (right - left) / area->aspect;
+            bottom = top + new_height;
         }
+        break;
 
-        area->crop.x = left;
-        area->crop.y = top;
-        area->crop.width = right - left + 1;
-        area->crop.height = bottom - top + 1;
+    case BOTTOM_RIGHT:
+        if (area->aspect < 0) {
+            bottom = y;
+            right = x;
+        }
+        else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
+            right = x;
+            new_height = (right - left) / area->aspect;
+            bottom = top + new_height;
+        }
+        else {
+            bottom = y;
+            new_width = (bottom - top) * area->aspect;
+            right = left + new_width;
+        }
+        break;
 
-        crop_to_widget (area, &damage);
-        gtk_widget_queue_draw_area (widget,
-                                    damage.x - 4, damage.y - 4,
-                                    damage.width + 6, damage.height + 6);
+    case BOTTOM:
+        bottom = y;
+        if (area->aspect > 0) {
+            new_width = (bottom - top) * area->aspect;
+            right= left + new_width;
+        }
+        break;
 
+    default:
         return FALSE;
-}
+    }
+
+    min_width = area->base_width / area->scale;
+    min_height = area->base_height / area->scale;
+
+    width = right - left + 1;
+    height = bottom - top + 1;
+    if (area->aspect < 0) {
+        if (left < 0)
+            left = 0;
+        if (top < 0)
+            top = 0;
+        if (right > pb_width)
+            right = pb_width;
+        if (bottom > pb_height)
+            bottom = pb_height;
 
-static gboolean
-cc_crop_area_button_press_event (GtkWidget      *widget,
-                                 GdkEventButton *event)
-{
-        CcCropArea *area = CC_CROP_AREA (widget);
-        GdkRectangle crop;
+        width = right - left + 1;
+        height = bottom - top + 1;
 
-        if (area->browse_pixbuf == NULL)
-                return FALSE;
+        switch (area->active_region) {
+        case LEFT:
+        case TOP_LEFT:
+        case BOTTOM_LEFT:
+            if (width < min_width)
+                left = right - min_width;
+            break;
+        case RIGHT:
+        case TOP_RIGHT:
+        case BOTTOM_RIGHT:
+            if (width < min_width)
+                right = left + min_width;
+            break;
 
-        crop_to_widget (area, &crop);
+        default: ;
+        }
+
+        switch (area->active_region) {
+        case TOP:
+        case TOP_LEFT:
+        case TOP_RIGHT:
+            if (height < min_height)
+                top = bottom - min_height;
+            break;
+        case BOTTOM:
+        case BOTTOM_LEFT:
+        case BOTTOM_RIGHT:
+            if (height < min_height)
+                bottom = top + min_height;
+            break;
 
-        area->last_press_x = (event->x - area->image.x) / area->scale;
-        area->last_press_y = (event->y - area->image.y) / area->scale;
-        area->active_region = find_location (&crop, event->x, event->y);
+        default: ;
+        }
+    }
+    else {
+        if (left < 0 || top < 0 ||
+            right > pb_width || bottom > pb_height ||
+            width < min_width || height < min_height) {
+            left = area->crop.x;
+            right = area->crop.x + area->crop.width - 1;
+            top = area->crop.y;
+            bottom = area->crop.y + area->crop.height - 1;
+        }
+    }
 
-        gtk_widget_queue_draw_area (widget,
-                                    crop.x - 4, crop.y - 4,
-                                    crop.width + 6, crop.height + 6);
+    area->crop.x = left;
+    area->crop.y = top;
+    area->crop.width = right - left + 1;
+    area->crop.height = bottom - top + 1;
 
-        return FALSE;
+    crop_to_widget (area, &damage);
+    // XXX
+    /* gtk_widget_queue_draw_area (GTK_WIDGET (area), */
+    /*                 damage.x - 4, damage.y - 4, */
+    /*                 damage.width + 6, damage.height + 6); */
+
+    return FALSE;
 }
 
 static gboolean
-cc_crop_area_button_release_event (GtkWidget      *widget,
-                                   GdkEventButton *event)
+on_button_pressed (GtkGestureClick *gesture,
+                   int n_press,
+                   double x,
+                   double y,
+                   gpointer user_data)
 {
-        CcCropArea *area = CC_CROP_AREA (widget);
-        GdkRectangle crop;
+    CcCropArea *area = CC_CROP_AREA (user_data);
+    GdkRectangle crop;
 
-        if (area->browse_pixbuf == NULL)
-                return FALSE;
+    if (area->browse_pixbuf == NULL)
+        return FALSE;
 
-        crop_to_widget (area, &crop);
+    crop_to_widget (area, &crop);
 
-        area->last_press_x = -1;
-        area->last_press_y = -1;
-        area->active_region = OUTSIDE;
+    area->last_press_x = (x - area->image.x) / area->scale;
+    area->last_press_y = (y - area->image.y) / area->scale;
+    area->active_region = find_location (&crop, x, y);
 
-        gtk_widget_queue_draw_area (widget,
-                                    crop.x - 4, crop.y - 4,
-                                    crop.width + 6, crop.height + 6);
+    // XXX
+    /* gtk_widget_queue_draw_area (GTK_WIDGET (area), */
+    /*                 crop.x - 4, crop.y - 4, */
+    /*                 crop.width + 6, crop.height + 6); */
 
-        return FALSE;
+    return GDK_EVENT_PROPAGATE;
 }
 
-static void
-cc_crop_area_set_size_request (CcCropArea *area)
+static gboolean
+on_button_released (GtkGestureClick *gesture,
+                    int n_press,
+                    double x,
+                    double y,
+                    gpointer user_data)
 {
-        gtk_widget_set_size_request (GTK_WIDGET (area),
-                                     area->base_width,
-                                     area->base_height);
+    CcCropArea *area = CC_CROP_AREA (user_data);
+    GdkRectangle crop;
+
+    if (area->browse_pixbuf == NULL)
+        return FALSE;
+
+    crop_to_widget (area, &crop);
+
+    area->last_press_x = -1;
+    area->last_press_y = -1;
+    area->active_region = OUTSIDE;
+
+    // XXX
+    /* gtk_widget_queue_draw_area (GTK_WIDGET (area), */
+    /*                 crop.x - 4, crop.y - 4, */
+    /*                 crop.width + 6, crop.height + 6); */
+
+    return GDK_EVENT_PROPAGATE;
 }
 
 static void
 cc_crop_area_finalize (GObject *object)
 {
-        CcCropArea *area = CC_CROP_AREA (object);
+    CcCropArea *area = CC_CROP_AREA (object);
 
-        if (area->browse_pixbuf) {
-                g_object_unref (area->browse_pixbuf);
-                area->browse_pixbuf = NULL;
-        }
-        if (area->pixbuf) {
-                g_object_unref (area->pixbuf);
-                area->pixbuf = NULL;
-        }
-        if (area->color_shifted) {
-                g_object_unref (area->color_shifted);
-                area->color_shifted = NULL;
-        }
+    g_clear_object (&area->browse_pixbuf);
+    g_clear_object (&area->pixbuf);
+    g_clear_object (&area->color_shifted);
 }
 
 static void
 cc_crop_area_class_init (CcCropAreaClass *klass)
 {
-        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
-        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-
-        object_class->finalize = cc_crop_area_finalize;
-        widget_class->draw = cc_crop_area_draw;
-        widget_class->button_press_event = cc_crop_area_button_press_event;
-        widget_class->button_release_event = cc_crop_area_button_release_event;
-        widget_class->motion_notify_event = cc_crop_area_motion_notify_event;
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->finalize = cc_crop_area_finalize;
 }
 
 static void
 cc_crop_area_init (CcCropArea *area)
 {
-        gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
-                               GDK_BUTTON_PRESS_MASK |
-                               GDK_BUTTON_RELEASE_MASK);
-
-        area->scale = 0.0;
-        area->image.x = 0;
-        area->image.y = 0;
-        area->image.width = 0;
-        area->image.height = 0;
-        area->active_region = OUTSIDE;
-        area->base_width = 48;
-        area->base_height = 48;
-        area->aspect = 1;
-
-        cc_crop_area_set_size_request (area);
+    GtkGesture *gesture;
+    GtkEventController *controller;
+
+    /* Draw function */
+    gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw, NULL, NULL);
+
+    /* Add handlers for clicks */
+    gesture = gtk_gesture_click_new ();
+    g_signal_connect (gesture, "pressed", G_CALLBACK (on_button_pressed), area);
+    g_signal_connect (gesture, "released", G_CALLBACK (on_button_released), area);
+    gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture));
+
+    /* Add handlers for motion events */
+    controller = gtk_event_controller_motion_new ();
+    g_signal_connect (controller, "motion", G_CALLBACK (on_motion), area);
+    gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (controller));
+
+    area->scale = 0.0;
+    area->image.x = 0;
+    area->image.y = 0;
+    area->image.width = 0;
+    area->image.height = 0;
+    area->active_region = OUTSIDE;
+    area->base_width = 48;
+    area->base_height = 48;
+    area->aspect = 1;
+
+    gtk_widget_set_size_request (GTK_WIDGET (area),
+                                 area->base_width,
+                                 area->base_height);
 }
 
 GtkWidget *
 cc_crop_area_new (void)
 {
-        return g_object_new (CC_TYPE_CROP_AREA, NULL);
+    return g_object_new (CC_TYPE_CROP_AREA, NULL);
 }
 
 GdkPixbuf *
 cc_crop_area_get_picture (CcCropArea *area)
 {
-        gint width, height;
+    int width, height;
 
-        width = gdk_pixbuf_get_width (area->browse_pixbuf);
-        height = gdk_pixbuf_get_height (area->browse_pixbuf);
-        width = MIN (area->crop.width, width - area->crop.x);
-        height = MIN (area->crop.height, height - area->crop.y);
+    width = gdk_pixbuf_get_width (area->browse_pixbuf);
+    height = gdk_pixbuf_get_height (area->browse_pixbuf);
+    width = MIN (area->crop.width, width - area->crop.x);
+    height = MIN (area->crop.height, height - area->crop.y);
 
-        return gdk_pixbuf_new_subpixbuf (area->browse_pixbuf,
-                                         area->crop.x,
-                                         area->crop.y,
-                                         width, height);
+    return gdk_pixbuf_new_subpixbuf (area->browse_pixbuf,
+                                     area->crop.x,
+                                     area->crop.y,
+                                     width, height);
 }
 
 void
 cc_crop_area_set_picture (CcCropArea *area,
                           GdkPixbuf  *pixbuf)
 {
-        int width;
-        int height;
+    int width = 0;
+    int height = 0;
 
-        if (area->browse_pixbuf) {
-                g_object_unref (area->browse_pixbuf);
-                area->browse_pixbuf = NULL;
-        }
-        if (pixbuf) {
-                area->browse_pixbuf = g_object_ref (pixbuf);
-                width = gdk_pixbuf_get_width (pixbuf);
-                height = gdk_pixbuf_get_height (pixbuf);
-        } else {
-                width = 0;
-                height = 0;
-        }
+    g_clear_object (&area->browse_pixbuf);
+    if (pixbuf) {
+        area->browse_pixbuf = g_object_ref (pixbuf);
+        width = gdk_pixbuf_get_width (pixbuf);
+        height = gdk_pixbuf_get_height (pixbuf);
+    }
 
-        area->crop.width = 2 * area->base_width;
-        area->crop.height = 2 * area->base_height;
-        area->crop.x = (width - area->crop.width) / 2;
-        area->crop.y = (height - area->crop.height) / 2;
+    area->crop.width = 2 * area->base_width;
+    area->crop.height = 2 * area->base_height;
+    area->crop.x = (width - area->crop.width) / 2;
+    area->crop.y = (height - area->crop.height) / 2;
 
-        area->scale = 0.0;
-        area->image.x = 0;
-        area->image.y = 0;
-        area->image.width = 0;
-        area->image.height = 0;
+    area->scale = 0.0;
+    area->image.x = 0;
+    area->image.y = 0;
+    area->image.width = 0;
+    area->image.height = 0;
 
-        gtk_widget_queue_draw (GTK_WIDGET (area));
+    // XXX
+    /* gtk_widget_queue_draw (GTK_WIDGET (area)); */
 }
 
 void
 cc_crop_area_set_min_size (CcCropArea *area,
-                           gint        width,
-                           gint        height)
+                           int         width,
+                           int         height)
 {
-        area->base_width = width;
-        area->base_height = height;
+    area->base_width = width;
+    area->base_height = height;
 
-        cc_crop_area_set_size_request (area);
+    gtk_widget_set_size_request (GTK_WIDGET (area),
+                                 area->base_width,
+                                 area->base_height);
 
-        if (area->aspect > 0) {
-                area->aspect = area->base_width / (gdouble)area->base_height;
-        }
+    if (area->aspect > 0)
+        area->aspect = area->base_width / (double) area->base_height;
 }
 
 void
 cc_crop_area_set_constrain_aspect (CcCropArea *area,
                                    gboolean    constrain)
 {
-        if (constrain) {
-                area->aspect = area->base_width / (gdouble)area->base_height;
-        }
-        else {
-                area->aspect = -1;
-        }
+    if (constrain) {
+        area->aspect = area->base_width / (double) area->base_height;
+    } else {
+        area->aspect = -1;
+    }
 }
-
diff --git a/src/contacts-accounts-list.vala b/src/contacts-accounts-list.vala
index 9690e917..8be6a1ab 100644
--- a/src/contacts-accounts-list.vala
+++ b/src/contacts-accounts-list.vala
@@ -18,26 +18,31 @@
 using Folks;
 
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-accounts-list.ui")]
-public class Contacts.AccountsList : Gtk.ListBox {
-  private Gtk.ListBoxRow last_selected_row;
+public class Contacts.AccountsList : Adw.Bin {
 
-  private Store contacts_store;
+  [GtkChild]
+  private unowned Gtk.ListBox listbox;
+  private unowned Gtk.ListBoxRow? last_selected_row = null;
 
-  public PersonaStore? selected_store;
+  private Store contacts_store;
+  public PersonaStore? selected_store = null;
 
   public signal void account_selected ();
 
+  construct {
+    this.listbox.row_activated.connect (on_row_activated);
+  }
+
   public AccountsList (Store contacts_store) {
     this.contacts_store = contacts_store;
-    this.selected_store = null;
   }
 
-  public override void row_activated (Gtk.ListBoxRow row) {
+  private void on_row_activated (Gtk.ListBox listbox, Gtk.ListBoxRow? row) {
     if (row == null)
       return;
 
-    if (last_selected_row != null &&
-        last_selected_row == row) {
+    if (this.last_selected_row != null &&
+        this.last_selected_row == row) {
       return;
     }
 
@@ -45,29 +50,33 @@ public class Contacts.AccountsList : Gtk.ListBox {
     checkmark.show ();
 
     if (last_selected_row != null) {
-      checkmark = last_selected_row.get_data<Gtk.Image> ("checkmark");
+      checkmark = this.last_selected_row.get_data<Gtk.Image> ("checkmark");
       if (checkmark != null)
         checkmark.hide ();
     }
 
-    last_selected_row = row;
-
-    selected_store = row.get_data<PersonaStore> ("store");
+    // Update the fields
+    this.last_selected_row = row;
+    this.selected_store = row.get_data<PersonaStore> ("store");
 
     account_selected ();
   }
 
   public void update_contents (bool select_active) {
-    foreach (var child in get_children ()) {
-      child.destroy ();
+    // Remove all entries
+    unowned var child = this.listbox.get_first_child ();
+    while (child != null) {
+      unowned var next = child.get_next_sibling ();
+      this.listbox.remove (child);
+      child = next;
     }
 
     // Fill the list with address book
     PersonaStore[] eds_stores = Utils.get_eds_address_books (this.contacts_store);
     debug ("Found %d EDS stores", eds_stores.length);
 
-    PersonaStore? local_store = null;
-    foreach (var persona_store in eds_stores) {
+    unowned PersonaStore? local_store = null;
+    foreach (unowned var persona_store in eds_stores) {
       if (persona_store.id == "system-address-book") {
         local_store = persona_store;
         continue;
@@ -85,63 +94,60 @@ public class Contacts.AccountsList : Gtk.ListBox {
         source_account_id = goa_source_ext.account_id;
       }
 
-      var row = new Hdy.ActionRow ();
+      var row = new Adw.ActionRow ();
       row.set_data ("store", persona_store);
 
       Gtk.Image provider_image;
       if (source_account_id != "")
         provider_image = Contacts.get_icon_for_goa_account (source_account_id);
       else
-        provider_image = new Gtk.Image.from_icon_name (Config.APP_ID,
-                                                       Gtk.IconSize.DIALOG);
+        provider_image = new Gtk.Image.from_icon_name (Config.APP_ID);
+      provider_image.icon_size = Gtk.IconSize.LARGE;
       row.add_prefix (provider_image);
       row.title = provider_name;
       row.subtitle = parent_source.display_name;
-      row.show_all ();
-      row.no_show_all = true;
-      var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic",
-                                                    Gtk.IconSize.MENU);
-      checkmark.set ("margin-end", 6,
-                     "valign", Gtk.Align.CENTER,
-                     "halign", Gtk.Align.END,
-                     "vexpand", true,
-                     "hexpand", true);
-      row.add (checkmark);
+
+      var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic");
+      checkmark.margin_end = 6;
+      checkmark.valign = Gtk.Align.CENTER;
+      checkmark.halign = Gtk.Align.END;
+      checkmark.hexpand = true;
+      checkmark.vexpand = true;
+      checkmark.visible = (persona_store == this.contacts_store.aggregator.primary_store);
+      row.add_suffix (checkmark);
       row.set_activatable_widget (checkmark);
       row.set_data ("checkmark", checkmark);
-      add (row);
+
+      this.listbox.append (row);
 
       if (select_active &&
           persona_store == this.contacts_store.aggregator.primary_store) {
-        row_activated (row);
+        this.listbox.row_activated (row);
       }
     }
 
     if (local_store != null) {
-      var local_row = new Hdy.ActionRow ();
-      var provider_image = new Gtk.Image.from_icon_name (Config.APP_ID,
-                                                         Gtk.IconSize.DIALOG);
+      var local_row = new Adw.ActionRow ();
+      var provider_image = new Gtk.Image.from_icon_name (Config.APP_ID);
+      provider_image.icon_size = Gtk.IconSize.LARGE;
       local_row.add_prefix (provider_image);
       local_row.title = _("Local Address Book");
-      local_row.show_all ();
-      local_row.no_show_all = true;
-      var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic", Gtk.IconSize.MENU);
-      checkmark.set ("margin-end", 6,
-                     "valign", Gtk.Align.CENTER,
-                     "halign", Gtk.Align.END,
-                     "vexpand", true,
-                     "hexpand", true);
-      local_row.add (checkmark);
+      var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic");
+      checkmark.margin_end = 6;
+      checkmark.valign = Gtk.Align.CENTER;
+      checkmark.halign = Gtk.Align.END;
+      checkmark.hexpand = true;
+      checkmark.vexpand = true;
+      checkmark.visible = (local_store == this.contacts_store.aggregator.primary_store);
+      local_row.add_suffix (checkmark);
       local_row.set_activatable_widget (checkmark);
       local_row.set_data ("checkmark", checkmark);
       local_row.set_data ("store", local_store);
-      add (local_row);
+      this.listbox.append (local_row);
       if (select_active &&
           local_store == this.contacts_store.aggregator.primary_store) {
-        row_activated (local_row);
+        this.listbox.row_activated (local_row);
       }
     }
-
-    show_all ();
   }
 }
diff --git a/src/contacts-addressbook-dialog.vala b/src/contacts-addressbook-dialog.vala
index f9cc8275..ab0fb926 100644
--- a/src/contacts-addressbook-dialog.vala
+++ b/src/contacts-addressbook-dialog.vala
@@ -31,36 +31,34 @@ public class Contacts.AddressbookDialog : Gtk.Dialog {
     add_buttons (_("Change"), Gtk.ResponseType.OK,
                  _("Cancel"), Gtk.ResponseType.CANCEL);
 
-    var content_area = get_content_area () as Gtk.Box;
-    content_area.border_width = 0;
-
     var ok_button = get_widget_for_response (Gtk.ResponseType.OK);
     ok_button.sensitive = false;
     ok_button.get_style_context ().add_class ("suggested-action");
 
-    var scrolled_window = new Gtk.ScrolledWindow (null, null);
-    scrolled_window.expand = true;
+    var scrolled_window = new Gtk.ScrolledWindow ();
+    scrolled_window.hexpand = true;
+    scrolled_window.vexpand = true;
     scrolled_window.height_request = 300;
     scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
     scrolled_window.propagate_natural_height = true;
-    content_area.add (scrolled_window);
+    ((Gtk.Box) this.get_content_area ()).append (scrolled_window);
 
-    var clamp = new Hdy.Clamp ();
+    var clamp = new Adw.Clamp ();
     clamp.margin_top = 32;
     clamp.margin_bottom = 32;
     clamp.margin_start = 12;
     clamp.margin_end = 12;
     clamp.maximum_size = 400;
-    scrolled_window.add (clamp);
+    scrolled_window.set_child (clamp);
 
     var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12);
     box.valign = Gtk.Align.START;
-    clamp.add (box);
+    clamp.set_child (box);
 
     var explanation_label = new Gtk.Label (_("New contacts will be added to the selected address book.\nYou 
are able to view and edit contacts from other address books."));
     explanation_label.xalign = 0;
     explanation_label.wrap = true;
-    box.add (explanation_label);
+    box.append (explanation_label);
 
     this.accounts_list = new AccountsList (contacts_store);
     this.accounts_list.update_contents (true);
@@ -72,12 +70,10 @@ public class Contacts.AddressbookDialog : Gtk.Dialog {
     });
 
     contacts_store.backend_store.backend_available.connect (() => {
-        this.accounts_list.update_contents (true);
+      this.accounts_list.update_contents (true);
     });
 
-    box.add (this.accounts_list);
-
-    show_all ();
+    box.append (this.accounts_list);
   }
 
   public override void response (int response) {
@@ -88,7 +84,7 @@ public class Contacts.AddressbookDialog : Gtk.Dialog {
     if (e_store != null) {
       eds_source_registry.set_default_address_book (e_store.source);
       var settings = new GLib.Settings ("org.freedesktop.folks");
-      settings.set_string ("primary-store", "eds:%s".printf(e_store.id));
+      settings.set_string ("primary-store", "eds:%s".printf (e_store.id));
     }
   }
 }
diff --git a/src/contacts-addressbook-list.vala b/src/contacts-addressbook-list.vala
index a7b26167..a5d2e8bb 100644
--- a/src/contacts-addressbook-list.vala
+++ b/src/contacts-addressbook-list.vala
@@ -19,18 +19,28 @@
 
 using Folks;
 
-public class Contacts.AddressbookList : Gtk.ListBox {
+public class Contacts.AddressbookList : Adw.Bin {
+
   private BackendStore store;
-  private AddressbookRow? marked_row;
   private bool show_icon;
 
+  private unowned Gtk.ListBox listbox;
+  private AddressbookRow? marked_row = null;
+
   public signal void addressbook_selected ();
 
+  construct {
+    var list_box = new Gtk.ListBox ();
+    list_box.row_activated.connect (on_row_activated);
+    list_box.set_header_func (list_box_update_header_func);
+    this.listbox = list_box;
+    this.child = this.listbox;
+  }
+
   public AddressbookList (BackendStore store, bool icon = true) {
     this.store = store;
     this.show_icon = icon;
 
-    this.set_header_func (list_box_update_header_func);
     this.update ();
   }
 
@@ -44,7 +54,7 @@ public class Contacts.AddressbookList : Gtk.ListBox {
     }
   }
 
-  public override void row_activated (Gtk.ListBoxRow row) {
+  private void on_row_activated (Gtk.ListBox listbox, Gtk.ListBoxRow row) {
     var addressbook = row as AddressbookRow;
     if (addressbook == null)
       return;
@@ -62,8 +72,12 @@ public class Contacts.AddressbookList : Gtk.ListBox {
   }
 
   public void update () {
-    foreach (var child in get_children ()) {
-      child.destroy ();
+    // Remove all entries
+    unowned var child = this.listbox.get_first_child ();
+    while (child != null) {
+      unowned var next = child.get_next_sibling ();
+      this.listbox.remove (child);
+      child = next;
     }
 
     // Fill the list with address book
@@ -93,25 +107,24 @@ public class Contacts.AddressbookList : Gtk.ListBox {
         if (source_account_id != "")
           provider_image = Contacts.get_icon_for_goa_account (source_account_id);
         else
-          provider_image = new Gtk.Image.from_icon_name (Config.APP_ID, Gtk.IconSize.DIALOG);
+          provider_image = new Gtk.Image.from_icon_name (Config.APP_ID);
       }
 
       var row = new AddressbookRow (provider_name, parent_source.display_name, provider_image);
-      add (row);
+      this.listbox.append (row);
     }
 
     if (local_store != null) {
-      var provider_image = this.show_icon? new Gtk.Image.from_icon_name (Config.APP_ID, Gtk.IconSize.DIALOG) 
: null;
+      var provider_image = this.show_icon? new Gtk.Image.from_icon_name (Config.APP_ID) : null;
       var local_row = new AddressbookRow (_("Local Address Book"), null, provider_image);
-      add (local_row);
+      this.listbox.append (local_row);
     }
-
-    show_all ();
   }
 }
 
-public class Contacts.AddressbookRow : Hdy.ActionRow {
+public class Contacts.AddressbookRow : Adw.ActionRow {
   Gtk.Widget checkmark;
+
   public AddressbookRow (string title, string? subtitle, Gtk.Widget? image = null) {
     this.set_selectable (false);
     if (image != null) {
@@ -121,15 +134,13 @@ public class Contacts.AddressbookRow : Hdy.ActionRow {
     if (subtitle != null) {
       this.subtitle = subtitle;
     }
-    this.show_all ();
-    this.no_show_all = true;
-    this.checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic", Gtk.IconSize.MENU);
+    this.checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic");
     this.checkmark.set ("margin-end", 6,
                         "valign", Gtk.Align.CENTER,
                         "halign", Gtk.Align.END,
                         "vexpand", true,
                         "hexpand", true);
-    this.add (this.checkmark);
+    this.set_child (this.checkmark);
   }
 
   public void unselect () {
diff --git a/src/contacts-app.vala b/src/contacts-app.vala
index b1adaf40..c746aadd 100644
--- a/src/contacts-app.vala
+++ b/src/contacts-app.vala
@@ -17,12 +17,12 @@
 
 using Folks;
 
-public class Contacts.App : Gtk.Application {
+public class Contacts.App : Adw.Application {
   private Settings settings;
 
   private Store contacts_store;
 
-  private MainWindow window;
+  private unowned MainWindow window;
 
   private const GLib.ActionEntry[] action_entries = {
     { "quit",             quit                },
@@ -30,7 +30,6 @@ public class Contacts.App : Gtk.Application {
     { "about",            show_about          },
     { "change-book",      change_address_book },
     { "online-accounts",  online_accounts     },
-    { "new-contact",      new_contact         },
     { "show-contact",     on_show_contact, "s"}
   };
 
@@ -45,6 +44,7 @@ public class Contacts.App : Gtk.Application {
   public App () {
     Object (
       application_id: Config.APP_ID,
+      resource_base_path: "/org/gnome/Contacts",
       flags: ApplicationFlags.HANDLES_COMMAND_LINE
     );
 
@@ -84,14 +84,14 @@ public class Contacts.App : Gtk.Application {
   }
 
   public void show_contact (Individual? individual) {
-    window.set_shown_contact (individual);
+    this.window.set_shown_contact (individual);
   }
 
   public async void show_individual (string id) {
     if (contacts_store.is_quiescent) {
       show_individual_ready.begin (id);
     } else {
-      contacts_store.quiescent.connect( () => {
+      contacts_store.quiescent.connect (() => {
         show_individual_ready.begin (id);
       });
     }
@@ -111,15 +111,15 @@ public class Contacts.App : Gtk.Application {
                                           Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
                                           _("No contact with id %s found"), id);
       dialog.set_title (_("Contact not found"));
-      dialog.run ();
-      dialog.destroy ();
+      dialog.response.connect ((_) => { dialog.close (); });
+      dialog.show ();
     }
   }
 
   public void change_address_book () {
     var dialog = new AddressbookDialog (this.contacts_store, this.window);
-    dialog.run ();
-    dialog.destroy ();
+    dialog.response.connect ((_) => dialog.close ());
+    dialog.show ();
   }
 
   public void online_accounts () {
@@ -131,7 +131,7 @@ public class Contacts.App : Gtk.Application {
                                               "/org/gnome/ControlCenter",
                                               "org.gtk.Actions");
 
-      var builder = new VariantBuilder (new VariantType ("av") );
+      var builder = new VariantBuilder (new VariantType ("av"));
       builder.add ("v", new Variant.string (""));
       var param = new Variant.tuple ({
         new Variant.string ("launch-panel"),
@@ -149,11 +149,8 @@ public class Contacts.App : Gtk.Application {
   }
 
   public void show_help () {
-    try {
-      Gtk.show_uri_on_window (window, "help:gnome-help/contacts", Gtk.get_current_event_time ());
-    } catch (GLib.Error e1) {
-      warning ("Error showing help: %s", e1.message);
-    }
+    // FIXME: use show_uri_full(), so we can report errors
+    Gtk.show_uri (this.window, "help:gnome-help/contacts", Gdk.CURRENT_TIME);
   }
 
   public void show_about () {
@@ -166,11 +163,10 @@ public class Contacts.App : Gtk.Application {
     string[] artists = {
       "Allan Day <allanpday gmail com>"
     };
-    Gtk.show_about_dialog (window,
+    Gtk.show_about_dialog (this.window,
                            "artists", artists,
                            "authors", authors,
                            "translator-credits", _("translator-credits"),
-                           "program-name", _("GNOME Contacts"),
                            "title", _("About GNOME Contacts"),
                            "comments", _("Contact Management Application"),
                            "copyright", _("© 2011 Red Hat, Inc.\n© 2011-2020 The Contacts Developers"),
@@ -182,7 +178,7 @@ public class Contacts.App : Gtk.Application {
   }
 
   public async void show_by_email (string email_address) {
-    var query = new SimpleQuery(email_address, { "email-addresses" });
+    var query = new SimpleQuery (email_address, { "email-addresses" });
     Individual individual = yield contacts_store.find_contact (query);
     if (individual != null) {
       show_contact (individual);
@@ -191,23 +187,25 @@ public class Contacts.App : Gtk.Application {
                                           Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
                                           _("No contact with email address %s found"), email_address);
       dialog.set_title (_("Contact not found"));
-      dialog.run ();
-      dialog.destroy ();
+      dialog.response.connect ((_) => dialog.close ());
+      dialog.show ();
     }
   }
 
   public void show_search (string query) {
     if (contacts_store.is_quiescent) {
-      window.show_search (query);
+      this.window.show_search (query);
     } else {
       contacts_store.quiescent.connect_after (() => {
-        window.show_search (query);
+        this.window.show_search (query);
       });
     }
   }
 
   private void create_window () {
-    this.window = new MainWindow (this.settings, this, this.contacts_store);
+    var win = new MainWindow (this.settings, this, this.contacts_store);
+    win.show ();
+    this.window = win;
 
     show_contact_list ();
   }
@@ -244,8 +242,6 @@ public class Contacts.App : Gtk.Application {
     this.contacts_store = new Store ();
     base.startup ();
 
-    Hdy.init ();
-
     load_styling ();
     create_actions ();
   }
@@ -254,16 +250,15 @@ public class Contacts.App : Gtk.Application {
     this.add_action_entries (action_entries, this);
 
     this.set_accels_for_action ("app.help", {"F1"});
-    this.set_accels_for_action ("app.new-contact", {"<Primary>n"});
-    this.set_accels_for_action ("win.show-help-overlay", {"<Primary>question"});
+    this.set_accels_for_action ("app.quit", {"<Control>q"});
   }
 
   public void load_styling () {
     var provider = new Gtk.CssProvider ();
     provider.load_from_resource ("/org/gnome/Contacts/ui/style.css");
-    Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(),
-                                              provider,
-                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+    Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (),
+                                               provider,
+                                               Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
   }
 
   public override void activate () {
@@ -275,28 +270,26 @@ public class Contacts.App : Gtk.Application {
   }
 
   private void run_setup () {
-    // Disable the change-book action (don't want the user to do that during setup)
-    var change_book_action = lookup_action ("change-book") as SimpleAction;
+    debug ("Running initial setup");
+
+    // Disable change-book action (don't want the user to do that during setup)
+    unowned var change_book_action = lookup_action ("change-book") as SimpleAction;
     change_book_action.set_enabled (false);
 
     // Create and show the setup window
     var setup_window = new SetupWindow (this, this.contacts_store);
-    setup_window.setup_done.connect ( (selected_store) => {
-        setup_window.destroy ();
+    setup_window.setup_done.connect ((selected_store) => {
+      setup_window.destroy ();
 
-        eds_source_registry.set_default_address_book (selected_store.source);
-        this.settings.did_initial_setup = true;
+      eds_source_registry.set_default_address_book (selected_store.source);
+      this.settings.did_initial_setup = true;
 
-        change_book_action.set_enabled (true); // re-enable change-book action
-        create_window ();
-      });
+      change_book_action.set_enabled (true);   // re-enable change-book action
+      create_window ();
+    });
     setup_window.show ();
   }
 
-  public void new_contact () {
-    this.window.new_contact ();
-  }
-
   private void on_show_contact(SimpleAction action, Variant? param) {
     activate();
 
diff --git a/src/contacts-avatar-selector.vala b/src/contacts-avatar-selector.vala
index 21790696..65da964d 100644
--- a/src/contacts-avatar-selector.vala
+++ b/src/contacts-avatar-selector.vala
@@ -21,18 +21,24 @@ const int MAIN_SIZE = 128;
 const int ICONS_SIZE = 64;
 
 private class Contacts.Thumbnail : Gtk.FlowBoxChild {
+
   public Gdk.Pixbuf? source_pixbuf { get; construct set; }
+
   private Thumbnail (Gdk.Pixbuf? source_pixbuf = null) {
-    Object (visible: true, halign : Gtk.Align.CENTER, source_pixbuf: source_pixbuf);
-    this.get_style_context ().add_class ("circular");
+    Object (visible: true,
+            halign: Gtk.Align.CENTER,
+            source_pixbuf: source_pixbuf);
+
+    this.add_css_class ("circular");
+
     var avatar = new Avatar (ICONS_SIZE);
     avatar.set_pixbuf (source_pixbuf);
-    add (avatar);
+    this.set_child (avatar);
   }
 
   public Thumbnail.for_persona (Persona persona) {
     Gdk.Pixbuf? pixbuf = null;
-    var details = persona as AvatarDetails;
+    unowned var details = persona as AvatarDetails;
     if (details != null && details.avatar != null) {
       try {
         var stream = details.avatar.load (MAIN_SIZE, null);
@@ -58,72 +64,43 @@ private class Contacts.Thumbnail : Gtk.FlowBoxChild {
 /**
  * The AvatarSelector can be used to choose the avatar for a contact.
  * This can be done by either choosing a stock thumbnail, an image file
- * provided by the user, or -if cheese is enabled- by using a webcam.
+ * provided by the user, or by using a webcam.
  *
  * After a user has initially chosen an avatar, we provide a cropping tool.
  */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-avatar-selector.ui")]
-public class Contacts.AvatarSelector : Gtk.Window {
+public class Contacts.AvatarSelector : Gtk.Dialog {
+
   const string AVATAR_BUTTON_CSS_NAME = "avatar-button";
 
-  // This will provide the default thumbnails
-  private Gnome.DesktopThumbnailFactory thumbnail_factory;
-  private Individual individual;
+  private unowned Individual individual;
 
   [GtkChild]
   private unowned Gtk.FlowBox thumbnail_grid;
 
-#if HAVE_CHEESE
   [GtkChild]
-  private unowned Gtk.Button cheese_button;
-  private int num_cameras;
-  private Cheese.CameraDeviceMonitor camera_monitor;
-#endif
+  private unowned Gtk.Button camera_button;
+
+  private Xdp.Portal? portal = null;
 
   public AvatarSelector (Individual? individual, Gtk.Window? window = null) {
-    Object (transient_for: window);
-    this.thumbnail_factory = new Gnome.DesktopThumbnailFactory (Gnome.ThumbnailSize.NORMAL);
+    Object (transient_for: window, use_header_bar: 1);
     this.individual = individual;
 
-    unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class (get_class ());
-    Gtk.BindingEntry.add_signal (binding_set,
-                                 Gdk.Key.Escape,
-                                 0,
-                                 "close",
-                                 0);
-
     update_thumbnail_grid ();
 
-#if HAVE_CHEESE
-    this.cheese_button.visible = true;
-    this.cheese_button.sensitive = false;
-
-    // Look for camera devices.
-    this.camera_monitor = new Cheese.CameraDeviceMonitor ();
-    this.camera_monitor.added.connect ( () => {
-        this.num_cameras++;
-        this.cheese_button.sensitive = (this.num_cameras > 0);
-      });
-    this.camera_monitor.removed.connect ( () => {
-        this.num_cameras--;
-        this.cheese_button.sensitive = (this.num_cameras > 0);
-      });
-    // Do this in a separate thread, or it blocks the whole UI
-    new Thread<void*> ("camera-loader", () => {
-        this.camera_monitor.coldplug ();
-        return null;
-      });
-#endif
+    this.setup_camera_portal.begin ();
   }
 
-  [Signal (action = true)]
-  public new virtual signal void close () {
-    base.close ();
-  }
+  private async void setup_camera_portal () {
+    this.portal = new Xdp.Portal ();
 
-  [GtkCallback]
-  public bool on_delete_event () {
-    return hide_on_delete ();
+    if (portal.is_camera_present ()) {
+      this.camera_button.sensitive = true;
+    } else {
+      this.camera_button.tooltip_text = _("No Camera Detected");
+      this.camera_button.sensitive = false;
+    }
   }
 
   private Gdk.Pixbuf scale_pixbuf_for_avatar_use (Gdk.Pixbuf pixbuf) {
@@ -134,10 +111,10 @@ public class Contacts.AvatarSelector : Gtk.Window {
       return pixbuf;
 
     if (w > h) {
-      h = (int)Math.round (h * (float) MAIN_SIZE / w);
+      h = (int) Math.round (h * (float) MAIN_SIZE / w);
       w = MAIN_SIZE;
     } else {
-      w = (int)Math.round (w * (float) MAIN_SIZE / h);
+      w = (int) Math.round (w * (float) MAIN_SIZE / h);
       h = MAIN_SIZE;
     }
 
@@ -150,19 +127,19 @@ public class Contacts.AvatarSelector : Gtk.Window {
       pixbuf.save_to_buffer (out buffer, "png", null);
       var icon = new BytesIcon (new Bytes (buffer));
       // Set the new avatar
-      this.individual.change_avatar.begin(icon as LoadableIcon, (obj, res) => {
+      this.individual.change_avatar.begin (icon as LoadableIcon, (obj, res) => {
         try {
-          this.individual.change_avatar.end(res);
+          this.individual.change_avatar.end (res);
         } catch (Error e) {
           warning ("Failed to set avatar: %s", e.message);
           Utils.show_error_dialog (_("Failed to set avatar."),
-                                   get_toplevel() as Gtk.Window);
+                                   get_root () as Gtk.Window);
         }
       });
     } catch (GLib.Error e) {
       warning ("Failed to set avatar: %s", e.message);
       Utils.show_error_dialog (_("Failed to set avatar."),
-                               get_toplevel() as Gtk.Window);
+                               get_root () as Gtk.Window);
     }
   }
   private void update_thumbnail_grid () {
@@ -170,7 +147,7 @@ public class Contacts.AvatarSelector : Gtk.Window {
       foreach (var p in individual.personas) {
         var widget = new Thumbnail.for_persona (p);
         if (widget.source_pixbuf != null)
-          this.thumbnail_grid.add (widget);
+          this.thumbnail_grid.insert (widget, -1);
       }
     }
 
@@ -178,33 +155,25 @@ public class Contacts.AvatarSelector : Gtk.Window {
     foreach (var file_name in stock_files) {
       var widget = new Thumbnail.for_filename (file_name);
       if (widget.source_pixbuf != null)
-        this.thumbnail_grid.add (widget);
+        this.thumbnail_grid.insert (widget, -1);
     }
-    this.thumbnail_grid.show_all ();
   }
 
   [GtkCallback]
-  private void on_cheese_clicked (Gtk.Button button) {
-    var dialog = new CropCheeseDialog.for_cheese (get_toplevel() as Gtk.Window);
-    dialog.show_all ();
-    dialog.picture_selected.connect ( (pix) => {
-      selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
-      this.close ();
-    });
+  private void on_camera_button_clicked (Gtk.Button button) {
+    var dialog = new CropDialog.for_camera_portal (this.portal,
+                                                   this.get_root () as Gtk.Window);
+    dialog.show ();
   }
 
-  [GtkCallback]
-  private void on_cancel_clicked (Gtk.Button button) {
-    this.close ();
-  }
-
-  [GtkCallback]
-  private void on_done_clicked (Gtk.Button button) {
-    var selected_children = thumbnail_grid.get_selected_children ();
-    if (selected_children != null) {
-      var thumbnail = (selected_children.data as Thumbnail);
-      if (thumbnail != null)
-        selected_pixbuf (scale_pixbuf_for_avatar_use (thumbnail.source_pixbuf));
+  public override void response (int response) {
+    if (response == Gtk.ResponseType.OK) {
+      var selected_children = thumbnail_grid.get_selected_children ();
+      if (selected_children != null) {
+        unowned var thumbnail = (selected_children.data as Thumbnail);
+        if (thumbnail != null)
+          selected_pixbuf (scale_pixbuf_for_avatar_use (thumbnail.source_pixbuf));
+      }
     }
 
     this.close ();
@@ -213,88 +182,45 @@ public class Contacts.AvatarSelector : Gtk.Window {
   [GtkCallback]
   private void on_file_clicked (Gtk.Button button) {
     var chooser = new Gtk.FileChooserNative (_("Browse for more pictures"),
-                                             get_toplevel () as Gtk.Window,
+                                             this.get_root () as Gtk.Window,
                                              Gtk.FileChooserAction.OPEN,
                                              _("_Open"), _("_Cancel"));
     chooser.set_modal (true);
-    chooser.set_local_only (false);
-    var preview = new Gtk.Image ();
-    preview.set_size_request (MAIN_SIZE, -1);
-    chooser.set_preview_widget (preview);
-    chooser.set_use_preview_label (false);
-    preview.show ();
-
-    chooser.update_preview.connect (update_preview);
-
-    var folder = Environment.get_user_special_dir (UserDirectory.PICTURES);
-    if (folder != null)
-      chooser.set_current_folder (folder);
-
-    chooser.response.connect ( (response) => {
-        if (response != Gtk.ResponseType.ACCEPT) {
-          chooser.destroy ();
-          return;
-        }
-        try {
-          var file = File.new_for_uri (chooser.get_uri ());
-          var in_stream = file.read ();
-          var pixbuf = new Gdk.Pixbuf.from_stream (in_stream, null);
-          in_stream.close ();
-          if (pixbuf.get_width () > MAIN_SIZE || pixbuf.get_height () > MAIN_SIZE) {
-            var dialog = new CropCheeseDialog.for_crop (get_toplevel () as Gtk.Window,
-                                                        pixbuf);
-            dialog.picture_selected.connect ( (pix) => {
-              selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
-            });
-            dialog.show_all();
-          } else {
-            selected_pixbuf (scale_pixbuf_for_avatar_use (pixbuf));
-          }
-        } catch (GLib.Error e) {
-          warning ("Failed to set avatar: %s", e.message);
-          Utils.show_error_dialog (_("Failed to set avatar."),
-                                   this.get_toplevel() as Gtk.Window);
-        }
 
-      chooser.destroy ();
-    });
-
-    chooser.run ();
-    this.close ();
-  }
-
-  private void update_preview (Gtk.FileChooser chooser) {
-    var uri = chooser.get_preview_uri ();
-    if (uri != null) {
-      Gdk.Pixbuf? pixbuf = null;
+    try {
+      unowned var pictures_folder = Environment.get_user_special_dir (UserDirectory.PICTURES);
+      if (pictures_folder != null)
+        chooser.set_current_folder (File.new_for_path (pictures_folder));
+    } catch (Error e) {
+      warning ("Couldn't set avatar selector to Pictures folder: %s", e.message);
+    }
 
-      var preview = chooser.get_preview_widget () as Gtk.Image;
+    chooser.response.connect ((response) => {
+      if (response != Gtk.ResponseType.ACCEPT) {
+        return;
+      }
 
-      var file = File.new_for_uri (uri);
       try {
-        var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
-                         FileQueryInfoFlags.NONE, null);
-        if (file_info != null) {
-          var mime_type = file_info.get_content_type ();
-
-          if (mime_type != null)
-            pixbuf = this.thumbnail_factory.generate_thumbnail (uri, mime_type);
+        var file = chooser.get_file ();
+        var in_stream = file.read ();
+        var pixbuf = new Gdk.Pixbuf.from_stream (in_stream, null);
+        in_stream.close ();
+        if (pixbuf.get_width () > MAIN_SIZE || pixbuf.get_height () > MAIN_SIZE) {
+          var dialog = new CropDialog.for_pixbuf (pixbuf,
+                                                  get_root () as Gtk.Window);
+          dialog.picture_selected.connect ((pix) => {
+            selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+          });
+          dialog.show ();
+        } else {
+          selected_pixbuf (scale_pixbuf_for_avatar_use (pixbuf));
         }
-      } catch (Error e) {
-        debug ("Couldn't generate thumbnail for file '%s': %s", uri, e.message);
+      } catch (GLib.Error e) {
+        warning ("Failed to set avatar: %s", e.message);
+        Utils.show_error_dialog (_("Failed to set avatar."),
+                                 this.get_root () as Gtk.Window);
       }
-
-      if (chooser is Gtk.Dialog)
-        ((Gtk.Dialog) chooser).set_response_sensitive (Gtk.ResponseType.ACCEPT,
-                                                       (pixbuf != null));
-
-      if (pixbuf != null)
-        preview.set_from_pixbuf (pixbuf);
-      else
-        preview.set_from_icon_name ("dialog-question", Gtk.IconSize.DIALOG);
-    }
-
-    chooser.set_preview_widget_active (true);
+    });
+    chooser.show ();
   }
-
 }
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index 33653065..27740c11 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -21,13 +21,16 @@ using Folks;
  * The Avatar of a Contact is responsible for showing an {@link Folks.Individual}'s
  * avatar, or a fallback if it's not available.
  */
-public class Contacts.Avatar : Gtk.Bin {
-  private Hdy.Avatar widget;
+public class Contacts.Avatar : Adw.Bin {
+
+  private int avatar_size;
 
   private unowned Individual? individual = null;
 
   public Avatar (int size, Individual? individual = null) {
     this.individual = individual;
+    this.avatar_size = size;
+
     string name = "";
     bool show_initials = false;
     if (this.individual != null) {
@@ -42,35 +45,38 @@ public class Contacts.Avatar : Gtk.Bin {
       }
     }
 
-    this.widget = new Hdy.Avatar (size, name, show_initials);
-    this.widget.set_image_load_func (size => load_avatar (size));
-    this.widget.show ();
-    add(this.widget);
+    this.child = new Adw.Avatar (size, name, show_initials);
+
+    // FIXME: ideally we lazy-load this only when we become visible for the
+    // first time
+    this.load_avatar.begin ();
+  }
+
+  private async void load_avatar() {
+    if (individual == null || individual.avatar == null)
+      return;
 
-    show ();
+    try {
+      var stream = yield this.individual.avatar.load_async (this.avatar_size,
+                                                            null);
+      var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (stream,
+                                                                    this.avatar_size,
+                                                                    this.avatar_size,
+                                                                    true);
+      this.set_pixbuf (pixbuf);
+    } catch (Error e) {
+      warning ("Couldn't load avatar of '%s': %s", this.individual.display_name, e.message);
+    }
   }
 
   /**
    * Manually set the avatar to the given pixbuf, even if the contact has an avatar.
    */
   public void set_pixbuf (Gdk.Pixbuf? a_pixbuf) {
-    this.widget.set_image_load_func (size => load_avatar (size, a_pixbuf));
-  }
-
-  private Gdk.Pixbuf? load_avatar (int size, Gdk.Pixbuf? pixbuf = null) {
-    if (pixbuf != null) {
-      return pixbuf.scale_simple (size, size, Gdk.InterpType.HYPER);
-    } else {
-      if (this.individual != null && this.individual.avatar != null) {
-        try {
-          var stream = this.individual.avatar.load (size, null);
-          return new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
-        } catch (Error e) {
-          debug ("Couldn't load avatar of contact %s. Reason: %s", this.individual.display_name, e.message);
-        }
-      }
-    }
-    return null;
+    if (a_pixbuf != null)
+      ((Adw.Avatar) this.child).set_custom_image (Gdk.Texture.for_pixbuf (a_pixbuf));
+    else
+      ((Adw.Avatar) this.child).set_icon_name ("avatar-default-symbolic");
   }
 
   /* Find a nice name to generate the label and color for the fallback avatar
@@ -78,29 +84,33 @@ public class Contacts.Avatar : Gtk.Bin {
    * as a display name which we don't want to have as a label
    */
   private string find_display_name () {
-    string name = "";
-    Persona primary_persona = null;
+    unowned Persona primary_persona = null;
     foreach (var p in this.individual.personas) {
       if (p.store.is_primary_store) {
         primary_persona = p;
         break;
       }
     }
-    name = look_up_alias_for_display_name (primary_persona);
-    if (name == "") {
-      foreach (var p in this.individual.personas) {
-        name = look_up_alias_for_display_name (p);
-      }
+
+    unowned string alias = look_up_alias_for_display_name (primary_persona);
+    if (alias != "")
+      return alias;
+
+    foreach (var p in this.individual.personas) {
+      alias = look_up_alias_for_display_name (p);
+      if (alias != "")
+        return alias;
     }
-    if (name == "") {
-      foreach (var p in this.individual.personas) {
-        name = look_up_name_details_for_display_name (p);
-      }
+
+    foreach (var p in this.individual.personas) {
+      string name = look_up_name_details_for_display_name (p);
+      if (name != "")
+        return name;
     }
-    return name;
+    return "";
   }
 
-  private string look_up_alias_for_display_name (Persona? p) {
+  private unowned string look_up_alias_for_display_name (Persona? p) {
     unowned var a = p as AliasDetails;
     if (a != null && a.alias != null)
       return a.alias;
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 85c34f74..05c7e10c 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
  * Copyright (C) 2019 Purism SPC
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,24 +23,30 @@ using Folks;
  * A widget that allows the user to edit a given {@link Contact}.
  */
 public class Contacts.ContactEditor : Gtk.Box {
+
   private Individual individual;
   private Gtk.Entry name_entry;
   private AvatarSelector avatar_selector = null;
   private Avatar avatar;
 
+  construct {
+    this.orientation = Gtk.Orientation.VERTICAL;
+    this.spacing = 12;
+
+    this.add_css_class ("contacts-contact-editor");
+  }
+
   public ContactEditor (Individual individual, IndividualAggregator aggregator) {
-    Object (orientation: Gtk.Orientation.VERTICAL, spacing: 24);
     this.individual = individual;
 
     Gtk.Box header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
-    header.add (create_avatar_button ());
-    header.add (create_name_entry ());
-    add (header);
+    header.append (create_avatar_button ());
+    header.append (create_name_entry ());
+    append (header);
 
     foreach (var p in individual.personas) {
-      add (new EditorPersona (p, aggregator));
+      append (new EditorPersona (p, aggregator));
     }
-    show_all ();
   }
 
   // Creates the contact's current avatar in a big button on top of the Editor
@@ -47,8 +54,8 @@ public class Contacts.ContactEditor : Gtk.Box {
     this.avatar = new Avatar (PROFILE_SIZE, this.individual);
 
     var button = new Gtk.Button ();
-    button.get_accessible ().set_name (_("Change avatar"));
-    button.image = this.avatar;
+    button.tooltip_text = _("Change avatar");
+    button.set_child (this.avatar);
     button.clicked.connect (on_avatar_button_clicked);
 
     return button;
@@ -57,8 +64,8 @@ public class Contacts.ContactEditor : Gtk.Box {
   // Show the avatar popover when the avatar is clicked
   private void on_avatar_button_clicked (Gtk.Button avatar_button) {
     if (this.avatar_selector == null)
-      this.avatar_selector = new AvatarSelector (this.individual, (Gtk.Window) this.get_toplevel());
-    this.avatar_selector.show();
+      this.avatar_selector = new AvatarSelector (this.individual, get_root () as Gtk.Window);
+    this.avatar_selector.show ();
   }
 
   // Creates the big name entry on the top
diff --git a/src/contacts-contact-list.vala b/src/contacts-contact-list.vala
index 266545d8..de381ad5 100644
--- a/src/contacts-contact-list.vala
+++ b/src/contacts-contact-list.vala
@@ -22,112 +22,81 @@ using Folks;
  * the left. It is contained by the {@link ListPane}, which also provides other
  * functionality, such as an action bar.
  */
-public class Contacts.ContactList : Gtk.ListBox {
-  private class ContactDataRow : Gtk.ListBoxRow {
-    private const int LIST_AVATAR_SIZE = 48;
+public class Contacts.ContactList : Adw.Bin {
 
-    public unowned Individual individual;
-    private Gtk.Label label;
-    private Avatar avatar;
-    public Gtk.CheckButton selector_button;
-
-    public ContactDataRow(Individual i) {
-      this.individual = i;
-      this.individual.notify.connect (on_contact_changed);
+  int nr_contacts_marked = 0;
 
-      get_style_context (). add_class ("contact-data-row");
+  private Query filter_query;
 
-      Gtk.Grid grid = new Gtk.Grid ();
-      grid.margin = 3;
-      grid.margin_start = 9;
-      grid.set_column_spacing (10);
-      this.avatar = new Avatar (LIST_AVATAR_SIZE, this.individual);
+  private Store store;
 
-      this.label = new Gtk.Label (individual.display_name);
-      this.label.ellipsize = Pango.EllipsizeMode.END;
-      this.label.valign = Gtk.Align.CENTER;
-      this.label.halign = Gtk.Align.START;
-      // Make sure it doesn't "twitch" when the checkbox becomes visible
-      this.label.xalign = 0;
+  private bool sort_on_surname = false; // keep in sync with the setting
 
-      this.selector_button = new Gtk.CheckButton ();
-      this.selector_button.visible = false;
-      this.selector_button.valign = Gtk.Align.CENTER;
-      this.selector_button.halign = Gtk.Align.END;
-      this.selector_button.hexpand = true;
-      // Make sure it doesn't overlap with the scrollbar
-      this.selector_button.margin_end = 12;
+  private bool got_long_press = false;
 
-      grid.attach (this.avatar, 0, 0);
-      grid.attach (this.label, 1, 0);
-      grid.attach (this.selector_button, 2, 0);
-      this.add (grid);
-      this.show_all ();
-    }
+  public UiState state { get; set; }
 
-    private void on_contact_changed (Object obj, ParamSpec pspec) {
-      //TODO: Update also the Avatar
-      this.label.set_text (this.individual.display_name);
-      changed ();
-    }
-  }
+  private unowned Gtk.ListBox listbox;
 
   public signal void selection_changed (Individual? individual);
   public signal void contacts_marked (int contacts_marked);
 
-  int nr_contacts_marked = 0;
+  construct {
+    var list_box = new Gtk.ListBox ();
+    this.listbox = list_box;
+    this.child = list_box;
 
-  private Query filter_query;
+    this.listbox.selection_mode = Gtk.SelectionMode.BROWSE;
+    this.listbox.set_sort_func (compare_rows);
+    this.listbox.set_filter_func (filter_row);
+    this.listbox.set_header_func (update_header);
+    this.listbox.add_css_class ("navigation-sidebar");
 
-  private Store store;
+    this.add_css_class ("contacts-contact-list");
 
-  private bool sort_on_surname = false; // keep in sync with the setting
+    // Row selection/activation
+    this.listbox.row_activated.connect (on_row_activated);
+    this.listbox.row_selected.connect (on_row_selected);
 
-  private Gtk.GestureLongPress long_press;
-  private bool got_long_press = false;
+    // Connect events right-click and long-press
+    var secondary_click_gesture = new Gtk.GestureClick ();
+    secondary_click_gesture.button = Gdk.BUTTON_SECONDARY;
+    secondary_click_gesture.pressed.connect (on_right_click);
+    this.listbox.add_controller (secondary_click_gesture);
 
-  public UiState state { get; set; }
+    var long_press_gesture = new Gtk.GestureLongPress ();
+    long_press_gesture.pressed.connect (on_long_press);
+    this.listbox.add_controller (long_press_gesture);
+  }
 
-  public ContactList (Settings settings, Store store, Query query) {
-    this.selection_mode = Gtk.SelectionMode.BROWSE;
+  public ContactList (Settings settings,
+                      Store    store,
+                      Query    query) {
     this.store = store;
     this.filter_query = query;
-    this.filter_query.notify.connect (() => { invalidate_filter (); });
-    this.visible = true;
+    this.filter_query.notify.connect (() => { this.listbox.invalidate_filter ();
+                                      });
 
     this.notify["state"].connect (on_ui_state_changed);
 
-    // Connect long press gesture
-    this.long_press = new Gtk.GestureLongPress (this);
-    this.long_press.pressed.connect ((g, x, y) => {
-      this.got_long_press = true;
-      var row = (ContactDataRow) get_row_at_y ((int) Math.round (y));
-      if (row != null) {
-        row.selector_button.active = this.state != UiState.SELECTING || !row.selector_button.active;
-      }
-    });
-
     this.sort_on_surname = settings.sort_on_surname;
-    settings.changed["sort-on-surname"].connect(() => {
-        this.sort_on_surname = settings.sort_on_surname;
-        invalidate_sort();
-      });
+    settings.changed["sort-on-surname"].connect (() => {
+      this.sort_on_surname = settings.sort_on_surname;
+      this.listbox.invalidate_sort ();
+    });
 
     this.store.added.connect (contact_added_cb);
     this.store.removed.connect (contact_removed_cb);
     foreach (var i in this.store.get_contacts ())
       contact_added_cb (this.store, i);
-
-    get_style_context ().add_class ("contacts-contact-list");
-
-    set_sort_func (compare_rows);
-    set_filter_func (filter_row);
-    set_header_func (update_header);
   }
 
   private void on_ui_state_changed (Object obj, ParamSpec pspec) {
-    foreach (var widget in get_children ()) {
-      var row = widget as ContactDataRow;
+    for (int i = 0; true; i++) {
+      unowned var row = (ContactDataRow) this.listbox.get_row_at_index (i);
+      if (row == null)
+        break;
+
       row.selector_button.visible = (this.state == UiState.SELECTING);
 
       if (this.state != UiState.SELECTING)
@@ -136,9 +105,9 @@ public class Contacts.ContactList : Gtk.ListBox {
 
     // Disalbe highlighted (blue) selection since we use the checkbox to show selection
     if (this.state == UiState.SELECTING) {
-      this.selection_mode = Gtk.SelectionMode.NONE;
+      this.listbox.selection_mode = Gtk.SelectionMode.NONE;
     } else {
-      this.selection_mode = Gtk.SelectionMode.BROWSE;
+      this.listbox.selection_mode = Gtk.SelectionMode.BROWSE;
       this.nr_contacts_marked = 0;
     }
   }
@@ -188,14 +157,12 @@ public class Contacts.ContactList : Gtk.ListBox {
   private Gtk.Label create_header_label (string text) {
     var label = new Gtk.Label (text);
     label.halign = Gtk.Align.START;
-    label.margin = 3;
     label.margin_start = 6;
+    label.margin_end = 3;
     label.margin_top = 6;
-    var attrs = new Pango.AttrList ();
-    attrs.insert (Pango.attr_weight_new (Pango.Weight.BOLD));
-    attrs.insert (Pango.attr_scale_new ((Pango.Scale.SMALL + Pango.Scale.MEDIUM) / 2.0));
-    attrs.insert (Pango.attr_foreground_alpha_new (30000));
-    label.attributes = attrs;
+    label.margin_bottom = 3;
+    label.add_css_class ("heading");
+    label.add_css_class ("dim-label");
     return label;
   }
 
@@ -206,7 +173,7 @@ public class Contacts.ContactList : Gtk.ListBox {
       row.selector_button.toggled.connect ( () => { on_row_checkbox_toggled (row); });
       row.selector_button.visible = (this.state == UiState.SELECTING);
 
-      add (row);
+      this.listbox.append (row);
     } else {
       debug ("Contact %s was ignored", i.id);
     }
@@ -228,9 +195,9 @@ public class Contacts.ContactList : Gtk.ListBox {
       row.destroy ();
   }
 
-  public override void row_activated (Gtk.ListBoxRow row) {
+  private void on_row_activated (Gtk.ListBox listbox, Gtk.ListBoxRow row) {
     if (!this.got_long_press) {
-      var data = row as ContactDataRow;
+      unowned var data = row as ContactDataRow;
       if (data != null && this.state == UiState.SELECTING)
         data.selector_button.active = !data.selector_button.active;
     } else {
@@ -238,10 +205,10 @@ public class Contacts.ContactList : Gtk.ListBox {
     }
   }
 
-  public override void row_selected (Gtk.ListBoxRow? row) {
+  private void on_row_selected (Gtk.ListBox listbox, Gtk.ListBoxRow? row) {
     if (this.state != UiState.SELECTING) {
-      var data = row as ContactDataRow;
-      var individual = data != null ? data.individual : null;
+      unowned var data = row as ContactDataRow;
+      unowned var individual = data != null? data.individual : null;
       selection_changed (individual);
 #if HAVE_TELEPATHY
       if (individual != null)
@@ -251,27 +218,26 @@ public class Contacts.ContactList : Gtk.ListBox {
   }
 
   private bool filter_row (Gtk.ListBoxRow row) {
-    var individual = ((ContactDataRow) row).individual;
+    unowned var individual = ((ContactDataRow) row).individual;
     return this.filter_query.is_match (individual) > 0;
   }
 
   public void select_contact (Individual? individual) {
     if (individual == null) {
       /* deselect */
-      select_row (null);
+      this.listbox.select_row (null);
       return;
     }
 
     ContactDataRow? row = find_row_for_contact (individual);
-    select_row (row);
+    this.listbox.select_row (row);
     scroll_to_contact (row);
   }
 
   public void scroll_to_contact (Gtk.ListBoxRow? row = null) {
     unowned ContactDataRow? selected_row = null;
-
     if (row == null)
-      selected_row = get_selected_row () as ContactDataRow;
+      selected_row = this.listbox.get_selected_row () as ContactDataRow;
     else
       selected_row = row as ContactDataRow;
 
@@ -288,10 +254,12 @@ public class Contacts.ContactList : Gtk.ListBox {
     }
   }
 
-
   private unowned ContactDataRow? find_row_for_contact (Individual individual) {
-    foreach (weak Gtk.Widget widget in get_children ()) {
-      unowned var row = ((ContactDataRow) widget);
+    for (int i = 0; true; i++) {
+      unowned var row = (ContactDataRow) this.listbox.get_row_at_index (i);
+      if (row == null)
+        break;
+
       if (row.individual == individual)
         return row;
     }
@@ -301,18 +269,27 @@ public class Contacts.ContactList : Gtk.ListBox {
 
   public Gee.LinkedList<Individual> get_marked_contacts () {
     var cs = new Gee.LinkedList<Individual> ();
-    foreach (weak Gtk.Widget widget in get_children ()) {
-      unowned var row = widget as ContactDataRow;
+
+    for (int i = 0; true; i++) {
+      unowned var row = (ContactDataRow) this.listbox.get_row_at_index (i);
+      if (row == null)
+        break;
+
       if (row.selector_button.active)
         cs.add (row.individual);
     }
+
     return cs;
   }
 
   public Gee.LinkedList<Individual> get_marked_contacts_and_hide () {
     var cs = new Gee.LinkedList<Individual> ();
-    foreach (weak Gtk.Widget widget in get_children ()) {
-      unowned var row = widget as ContactDataRow;
+
+    for (int i = 0; true; i++) {
+      unowned var row = (ContactDataRow) this.listbox.get_row_at_index (i);
+      if (row == null)
+        break;
+
       if (row.selector_button.active) {
         row.visible = false;
         cs.add (row.individual);
@@ -321,17 +298,71 @@ public class Contacts.ContactList : Gtk.ListBox {
     return cs;
   }
 
+  private void on_right_click (Gtk.GestureClick gesture, int n_press, double x, double y) {
+    unowned var row = (ContactDataRow) this.listbox.get_row_at_y ((int) Math.round (y));
+    if (row != null) {
+      row.selector_button.active = this.state != UiState.SELECTING || !row.selector_button.active;
+    }
+  }
+
+  private void on_long_press (Gtk.GestureLongPress gesture, double x, double y) {
+    this.got_long_press = true;
+    var row = (ContactDataRow) this.listbox.get_row_at_y ((int) Math.round (y));
+    if (row != null) {
+      row.selector_button.active = this.state != UiState.SELECTING || !row.selector_button.active;
+    }
+  }
 
-  public override bool button_press_event (Gdk.EventButton event) {
-    base.button_press_event (event);
+  // A class for the ListBoxRows
+  private class ContactDataRow : Gtk.ListBoxRow {
+    private const int LIST_AVATAR_SIZE = 48;
 
-    if (event.button == Gdk.BUTTON_SECONDARY) {
-      unowned var row = (ContactDataRow) get_row_at_y ((int) Math.round (event.y));
-      if (row != null) {
-        row.selector_button.active = this.state != UiState.SELECTING || !row.selector_button.active;
-      }
+    public unowned Individual individual;
+    private unowned Gtk.Label label;
+    private unowned Avatar avatar;
+    public unowned Gtk.CheckButton selector_button;
+
+    public ContactDataRow (Individual i) {
+      this.individual = i;
+      this.individual.notify.connect (on_contact_changed);
+
+      add_css_class ("contact-data-row");
+
+      var box = new Gtk.Box (HORIZONTAL, 12);
+      box.margin_top = 6;
+      box.margin_bottom = 6;
+
+      var avatar = new Avatar (LIST_AVATAR_SIZE, this.individual);
+      box.append (avatar);
+      this.avatar = avatar;
+
+      var label = new Gtk.Label (individual.display_name);
+      label.ellipsize = Pango.EllipsizeMode.END;
+      label.valign = Gtk.Align.CENTER;
+      label.halign = Gtk.Align.START;
+      // Make sure it doesn't "twitch" when the checkbox becomes visible
+      label.xalign = 0;
+      box.append (label);
+      this.label = label;
+
+      var selector_button = new Gtk.CheckButton ();
+      selector_button.visible = false;
+      selector_button.valign = Gtk.Align.CENTER;
+      selector_button.halign = Gtk.Align.END;
+      selector_button.hexpand = true;
+      selector_button.add_css_class ("selection-mode");
+      // Make sure it doesn't overlap with the scrollbar
+      selector_button.margin_end = 12;
+      box.append (selector_button);
+      this.selector_button = selector_button;
+
+      this.set_child (box);
     }
 
-    return false;
+    private void on_contact_changed (Object obj, ParamSpec pspec) {
+      //TODO: Update also the Avatar
+      this.label.set_text (this.individual.display_name);
+      changed ();
+    }
   }
 }
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index bb52992e..97262161 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,7 +26,7 @@ const int PROFILE_SIZE = 128;
  * and a ContactEditor to edit contact information.
  */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-contact-pane.ui")]
-public class Contacts.ContactPane : Gtk.Bin {
+public class Contacts.ContactPane : Adw.Bin {
 
   private MainWindow main_window;
 
@@ -37,21 +38,12 @@ public class Contacts.ContactPane : Gtk.Bin {
   private unowned Gtk.Stack stack;
 
   [GtkChild]
-  private unowned Hdy.StatusPage none_selected_page;
+  private unowned Adw.Clamp contact_sheet_clamp;
+  private unowned ContactSheet? sheet = null;
 
   [GtkChild]
-  private unowned Gtk.ScrolledWindow contact_sheet_view;
-
-  [GtkChild]
-  private unowned Gtk.Container contact_sheet_page;
-  private ContactSheet? sheet = null;
-
-  [GtkChild]
-  private unowned Gtk.ScrolledWindow contact_editor_view;
-
-  [GtkChild]
-  private unowned Gtk.Box contact_editor_page;
-  private ContactEditor? editor = null;
+  private unowned Gtk.Box contact_editor_box;
+  private unowned ContactEditor? editor = null;
 
   public bool on_edit_mode = false;
   private LinkSuggestionGrid? suggestion_grid = null;
@@ -70,28 +62,29 @@ public class Contacts.ContactPane : Gtk.Bin {
   }
 
   public void add_suggestion (Individual i) {
-    var parent_overlay = this.get_parent () as Gtk.Overlay;
+    unowned var parent_overlay = this.get_parent () as Gtk.Overlay;
 
     remove_suggestion_grid ();
     this.suggestion_grid = new LinkSuggestionGrid (i);
+    this.suggestion_grid.valign = Gtk.Align.END;
     parent_overlay.add_overlay (this.suggestion_grid);
 
-    this.suggestion_grid.suggestion_accepted.connect ( () => {
-        var linked_contact = this.individual.display_name;
-        var operation = new LinkOperation (this.store);
-        var to_link = new Gee.LinkedList<Individual> ();
-        to_link.add (this.individual);
-        to_link.add (i);
-        operation.execute.begin (to_link);
-        this.contacts_linked (null, linked_contact, operation);
-        remove_suggestion_grid ();
-      });
-
-    this.suggestion_grid.suggestion_rejected.connect ( () => {
-        /* TODO: Add undo */
-        store.add_no_suggest_link (this.individual, i);
-        remove_suggestion_grid ();
-      });
+    this.suggestion_grid.suggestion_accepted.connect (() => {
+      var linked_contact = this.individual.display_name;
+      var to_link = new Gee.LinkedList<Individual> ();
+      to_link.add (this.individual);
+      to_link.add (i);
+      var operation = new LinkOperation (this.store, to_link);
+      operation.execute.begin ();
+      this.contacts_linked (null, linked_contact, operation);
+      remove_suggestion_grid ();
+    });
+
+    this.suggestion_grid.suggestion_rejected.connect (() => {
+      /* TODO: Add undo */
+      store.add_no_suggest_link (this.individual, i);
+      remove_suggestion_grid ();
+    });
   }
 
   public void show_contact (Individual? individual) {
@@ -104,17 +97,19 @@ public class Contacts.ContactPane : Gtk.Bin {
       show_contact_sheet ();
     } else {
       remove_contact_sheet ();
-      this.stack.set_visible_child (this.none_selected_page);
+      this.stack.set_visible_child_name ("none-selected-page");
     }
   }
 
   private void show_contact_sheet () {
-    assert (this.individual != null);
+    return_if_fail (this.individual != null);
 
-    remove_contact_sheet();
-    this.sheet = new ContactSheet (this.individual, this.store);
-    this.contact_sheet_page.add (this.sheet);
-    this.stack.set_visible_child (this.contact_sheet_view);
+    remove_contact_sheet ();
+    var contacts_sheet = new ContactSheet (this.individual, this.store);
+    contacts_sheet.hexpand = true;
+    this.sheet = contacts_sheet;
+    this.contact_sheet_clamp.set_child (this.sheet);
+    this.stack.set_visible_child_name ("contact-sheet-page");
 
     var matches = this.store.aggregator.get_potential_matches (this.individual, MatchResult.HIGH);
     foreach (var i in matches.keys) {
@@ -132,24 +127,24 @@ public class Contacts.ContactPane : Gtk.Bin {
     // Remove the suggestion grid that goes along with it.
     remove_suggestion_grid ();
 
-    this.contact_sheet_page.remove (this.sheet);
-    this.sheet.destroy();
+    this.contact_sheet_clamp.set_child (null);
     this.sheet = null;
   }
 
   private void create_contact_editor () {
     remove_contact_editor ();
 
-    this.editor = new ContactEditor (this.individual, store.aggregator);
+    var contact_editor = new ContactEditor (this.individual, store.aggregator);
+    this.editor = contact_editor;
 
-    this.contact_editor_page.add (this.editor);
+    this.contact_editor_box.append (this.editor);
   }
 
   private void remove_contact_editor () {
     if (this.editor == null)
       return;
 
-    this.contact_editor_page.remove (this.editor);
+    this.contact_editor_box.remove (this.editor);
     this.editor = null;
   }
 
@@ -160,7 +155,7 @@ public class Contacts.ContactPane : Gtk.Bin {
     this.on_edit_mode = true;
 
     create_contact_editor ();
-    this.stack.set_visible_child (this.contact_editor_view);
+    this.stack.set_visible_child_name ("contact-editor-page");
   }
 
   public void stop_editing (bool cancel = false) {
@@ -175,9 +170,9 @@ public class Contacts.ContactPane : Gtk.Bin {
       if (fake_individual != null && fake_individual.real_individual != null) {
         // Reset individual on to the real one
         this.individual = fake_individual.real_individual;
-        this.stack.set_visible_child (this.contact_sheet_view);
+        this.stack.set_visible_child_name ("contact-sheet-page");
       } else {
-        this.stack.set_visible_child (this.none_selected_page);
+        this.stack.set_visible_child_name ("none-selected-page");
       }
       return;
     }
@@ -220,10 +215,10 @@ public class Contacts.ContactPane : Gtk.Bin {
       writeable_properties = {};
     }
 
-    var fake_persona = new FakePersona (FakePersonaStore.the_store(), writeable_properties, details);
+    var fake_persona = new FakePersona (FakePersonaStore.the_store (), writeable_properties, details);
     var fake_personas = new Gee.HashSet<FakePersona> ();
     fake_personas.add (fake_persona);
-    this.individual = new FakeIndividual(fake_personas);
+    this.individual = new FakeIndividual (fake_personas);
 
     start_editing ();
   }
@@ -262,20 +257,21 @@ public class Contacts.ContactPane : Gtk.Bin {
 
   private void show_message_dialog (string message) {
     var dialog =
-        new Gtk.MessageDialog (this.main_window,
-                               Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
-                               Gtk.MessageType.ERROR,
-                               Gtk.ButtonsType.OK,
-                               "%s", message);
-    dialog.run ();
-    dialog.destroy ();
+      new Gtk.MessageDialog (this.main_window,
+                             Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
+                             Gtk.MessageType.ERROR,
+                             Gtk.ButtonsType.OK,
+                             "%s", message);
+    dialog.response.connect ((_) => dialog.close ());
+    dialog.show ();
   }
 
   private void remove_suggestion_grid () {
     if (this.suggestion_grid == null)
       return;
 
-    this.suggestion_grid.destroy ();
+    unowned var parent_overlay = this.get_parent () as Gtk.Overlay;
+    parent_overlay.remove_overlay (suggestion_grid);
     this.suggestion_grid = null;
   }
 }
diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala
index ceb41a54..57e53bdb 100644
--- a/src/contacts-contact-sheet.vala
+++ b/src/contacts-contact-sheet.vala
@@ -17,16 +17,43 @@
 
 using Folks;
 
+// XXX accesibility?
+public class Contacts.ContactSheetRow : Adw.ActionRow {
+
+  public ContactSheetRow (string property_name, string title, string? subtitle = null) {
+    unowned var icon_name = Utils.get_icon_name_for_property (property_name);
+    if (icon_name != null) {
+      var icon = new Gtk.Image.from_icon_name (icon_name);
+      icon.add_css_class ("contacts-property-icon");
+      icon.tooltip_text = Utils.get_display_name_for_property (property_name);
+      this.add_prefix (icon);
+    }
+
+    this.title = Markup.escape_text (title);
+
+    if (subtitle != null)
+      this.subtitle = subtitle;
+  }
+
+  public Gtk.Button add_button (string icon) {
+    var button = new Gtk.Button.from_icon_name (icon);
+    button.valign = Gtk.Align.CENTER;
+    button.add_css_class ("flat");
+    this.add_suffix (button);
+    return button;
+  }
+}
+
 /**
  * The contact sheet displays the actual information of a contact.
  *
  * (Note: to edit a contact, use the {@link ContactEditor} instead.
  */
 public class Contacts.ContactSheet : Gtk.Grid {
+
   private int last_row = 0;
-  private Individual individual;
+  private unowned Individual individual;
   private unowned Store store;
-  public bool narrow { get; set; default = true; }
 
   private const string[] SORTED_PROPERTIES = {
     "email-addresses",
@@ -39,8 +66,11 @@ public class Contacts.ContactSheet : Gtk.Grid {
     "notes"
   };
 
+  construct {
+    this.add_css_class ("contacts-sheet");
+  }
+
   public ContactSheet (Individual individual, Store store) {
-    Object (row_spacing: 12, column_spacing: 12);
     this.individual = individual;
     this.store = store;
 
@@ -56,140 +86,112 @@ public class Contacts.ContactSheet : Gtk.Grid {
     var attrList = new Pango.AttrList ();
     attrList.insert (Pango.attr_weight_new (Pango.Weight.BOLD));
     store_name.set_attributes (attrList);
-    store_name.set_halign (Gtk.Align.START);
-    store_name.set_ellipsize (Pango.EllipsizeMode.MIDDLE);
+    store_name.halign = Gtk.Align.START;
+    store_name.ellipsize = Pango.EllipsizeMode.MIDDLE;
 
     return store_name;
   }
 
-  private Gtk.Button create_button (string icon) {
-    var button = new Gtk.Button.from_icon_name (icon, Gtk.IconSize.BUTTON);
-    button.set_halign (Gtk.Align.END);
-    button.get_style_context ().add_class ("flatten");
+  // Helper function that attaches a row to our grid
+  private void attach_row (Gtk.ListBoxRow row) {
+    var list_box = new Gtk.ListBox ();
+    list_box.selection_mode = Gtk.SelectionMode.NONE;
+    list_box.add_css_class ("boxed-list");
+    list_box.add_css_class ("contacts-sheet-property");
+    list_box.append (row);
 
-    return button;
-  }
-
-  void add_row_with_label (string label_value,
-                           string value,
-                           Gtk.Widget? btn1 = null,
-                           Gtk.Widget? btn2 =null) {
-    if (value == "" || value == null)
-      return;
-    var type_label = new Gtk.Label (label_value);
-    type_label.xalign = 1.0f;
-    type_label.set_halign (Gtk.Align.END);
-    type_label.set_valign (Gtk.Align.CENTER);
-    type_label.get_style_context ().add_class ("dim-label");
-    this.attach (type_label, 0, this.last_row, 1, 1);
-
-    var value_label = new Gtk.Label (value);
-    value_label.set_line_wrap (true);
-    value_label.xalign = 0.0f;
-    value_label.set_halign (Gtk.Align.START);
-    value_label.set_ellipsize (Pango.EllipsizeMode.END);
-    value_label.wrap_mode = Pango.WrapMode.CHAR;
-    value_label.set_selectable (true);
-    value_label.set_can_focus (false);
-
-    if (btn1 != null || btn2 !=null) {
-      var value_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12);
-      value_box.pack_start (value_label, false, false, 0);
-
-      if (btn1 != null)
-        value_box.pack_end (btn1, false, false, 0);
-      if (btn2 != null)
-        value_box.pack_end (btn2, false, false, 0);
-      this.attach (value_box, 1, this.last_row, 1, 1);
-    } else {
-      this.attach (value_label, 1, this.last_row, 1, 1);
-    }
+    this.attach (list_box, 0, this.last_row, 3, 1);
     this.last_row++;
   }
 
   private void update () {
     this.last_row = 0;
-    this.foreach ((child) => this.remove (child));
 
-    var image_frame = new Avatar (PROFILE_SIZE, this.individual);
-    image_frame.set_vexpand (false);
-    image_frame.set_valign (Gtk.Align.START);
-
-    this.attach (image_frame,  0, 0, 1, 3);
+    // Remove all fields
+    unowned var child = get_first_child ();
+    while (child != null) {
+      unowned var next = child.get_next_sibling ();
+      remove (child);
+      child = next;
+    }
 
-    create_name_label ();
+    var header = create_header ();
+    this.attach (header, 0, 0, 1, 1);
 
-    this.last_row += 3; // Name/Avatar takes up 3 rows
+    this.last_row++;
 
     var personas = 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);
+    for (int i = 0; i < personas.get_n_items (); i++) {
+      var p = (Persona) personas.get_item (i);
       int persona_store_pos = this.last_row;
-      if (!is_first_persona) {
+
+      if (i > 0) {
         this.attach (create_persona_store_label (p), 0, this.last_row, 3);
         this.last_row++;
       }
 
-      foreach (var prop in SORTED_PROPERTIES)
+      foreach (unowned var prop in SORTED_PROPERTIES)
         add_row_for_property (p, prop);
 
       // Nothing to show in the persona: don't mention it
       bool is_empty_persona = (this.last_row == persona_store_pos + 1);
-      if (!is_first_persona && is_empty_persona) {
+      if (i > 0 && is_empty_persona) {
         this.remove_row (persona_store_pos);
         this.last_row--;
       }
     }
-
-    show_all ();
   }
 
-  private void update_name_label (Gtk.Label name_label) {
-    var name = Markup.printf_escaped ("<span font='16'>%s</span>",
-                                      this.individual.display_name);
-    name_label.set_markup (name);
-  }
+  private Gtk.Widget create_header () {
+    var header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 18);
+    header.add_css_class ("contacts-sheet-header");
+
+    var image_frame = new Avatar (PROFILE_SIZE, this.individual);
+    image_frame.vexpand = false;
+    image_frame.valign = Gtk.Align.START;
+    header.append (image_frame);
 
-  private void create_name_label () {
     var name_label = new Gtk.Label ("");
-    name_label.ellipsize = Pango.EllipsizeMode.END;
+    name_label.set_markup (this.individual.display_name);
+    name_label.hexpand = true;
     name_label.xalign = 0f;
+    name_label.wrap = true;
     name_label.lines = 4;
+    name_label.width_chars = 10;
     name_label.selectable = true;
-    name_label.set_can_focus (false);
-    this.attach (name_label,  1, 0, 1, 3);
-    update_name_label (name_label);
-    this.individual.notify["display-name"].connect ((obj, spec) => {
-      update_name_label (name_label);
-    });
+    name_label.can_focus = false;
+    name_label.add_css_class ("large-title");
+    header.append (name_label);
+
+    return header;
   }
 
   private void add_row_for_property (Persona persona, string property) {
     switch (property) {
       case "email-addresses":
-        add_emails (persona);
+        add_emails (persona, property);
         break;
       case "phone-numbers":
-        add_phone_nrs (persona);
+        add_phone_nrs (persona, property);
         break;
       case "im-addresses":
-        add_im_addresses (persona);
+        add_im_addresses (persona, property);
         break;
       case "urls":
-        add_urls (persona);
+        add_urls (persona, property);
         break;
       case "nickname":
-        add_nickname (persona);
+        add_nickname (persona, property);
         break;
       case "birthday":
-        add_birthday (persona);
+        add_birthday (persona, property);
         break;
       case "notes":
-        add_notes (persona);
+        add_notes (persona, property);
         break;
       case "postal-addresses":
-        add_postal_addresses (persona);
+        add_postal_addresses (persona, property);
         break;
       default:
         debug ("Unsupported property: %s", property);
@@ -197,97 +199,114 @@ public class Contacts.ContactSheet : Gtk.Grid {
     }
   }
 
-  private void add_emails (Persona persona) {
-    var details = persona as EmailDetails;
-    if (details != null) {
-      var emails = Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
-      foreach (var email in emails) {
-        var button = create_button ("mail-unread-symbolic");
-        button.clicked.connect (() => {
-          Utils.compose_mail ("%s <%s>".printf(this.individual.display_name, email.value));
-        });
-        add_row_with_label (TypeSet.email.format_type (email), email.value, button);
-      }
+  private void add_emails (Persona persona, string property) {
+    unowned var details = persona as EmailDetails;
+    if (details == null)
+      return;
+
+    var emails = Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
+    foreach (var email in emails) {
+      if (email.value == "")
+        continue;
+
+      var row = new ContactSheetRow (property,
+                                     email.value,
+                                     TypeSet.email.format_type (email));
+
+      var button = row.add_button ("mail-send-symbolic");
+      button.tooltip_text = _("Send an email to %s".printf (email.value));
+      button.clicked.connect (() => {
+        Utils.compose_mail ("%s <%s>".printf(this.individual.display_name, email.value));
+      });
+
+      this.attach_row (row);
     }
   }
 
-  private void add_phone_nrs (Persona persona) {
-    var phone_details = persona as PhoneDetails;
-    if (phone_details != null) {
-      var phones = Utils.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
-      foreach (var phone in phones) {
+  private void add_phone_nrs (Persona persona, string property) {
+    unowned var phone_details = persona as PhoneDetails;
+    if (phone_details == null)
+      return;
+
+    var phones = Utils.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
+    foreach (var phone in phones) {
+      if (phone.value == "")
+        continue;
+
+      var row = new ContactSheetRow (property,
+                                     phone.value,
+                                     TypeSet.phone.format_type (phone));
+
 #if HAVE_TELEPATHY
-        if (this.store.caller_account != null) {
-          var call_button = create_button ("call-start-symbolic");
-          call_button.clicked.connect (() => {
-            Utils.start_call (phone.value, this.store.caller_account);
-          });
-
-          add_row_with_label (TypeSet.phone.format_type (phone), phone.value, call_button);
-        } else {
-          add_row_with_label (TypeSet.phone.format_type (phone), phone.value);
-        }
-#else
-        add_row_with_label (TypeSet.phone.format_type (phone), phone.value);
-#endif
+      if (this.store.caller_account != null) {
+        var button = row.add_button ("call-start-symbolic");
+        button.tooltip_text = _("Start a call");
+        button.clicked.connect (() => {
+          Utils.start_call (phone.value, this.store.caller_account);
+        });
       }
+#endif
+
+      this.attach_row (row);
     }
   }
 
-  private void add_im_addresses (Persona persona) {
+  private void add_im_addresses (Persona persona, string property) {
 #if HAVE_TELEPATHY
-    var im_details = persona as ImDetails;
-    if (im_details != null) {
-      foreach (var protocol in im_details.im_addresses.get_keys ()) {
-        foreach (var id in im_details.im_addresses[protocol]) {
-          if (persona is Tpf.Persona) {
-            var button = create_button ("user-available-symbolic");
-            button.clicked.connect (() => {
-              var im_persona = 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.individual, protocol, id.value);
-                }
-              }
-            });
-            add_row_with_label (ImService.get_display_name (protocol), id.value, button);
+    unowned var im_details = persona as ImDetails;
+    if (im_details == null)
+      return;
+
+    foreach (var protocol in im_details.im_addresses.get_keys ()) {
+      foreach (var id in im_details.im_addresses[protocol]) {
+        if (!(persona is Tpf.Persona))
+          continue;
+
+        var row = new ContactSheetRow (property,
+                                       id.value,
+                                       ImService.get_display_name (protocol));
+        var button = row.add_button ("user-available-symbolic");
+        button.clicked.connect (() => {
+          var im_persona = 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.individual, protocol, id.value);
+            }
           }
-        }
+        });
+        this.attach_row (row);
       }
     }
 #endif
   }
 
-  private void add_urls (Persona persona) {
-    var url_details = persona as UrlDetails;
-    if (url_details != null) {
-      foreach (var url in url_details.urls) {
-        var button = create_button ("web-browser-symbolic");
-        button.clicked.connect (() => {
-          unowned var window = button.get_toplevel () as MainWindow;
-          if (window == null)
-            return;
-
-          try {
-            Gtk.show_uri_on_window (window,
-                                    fallback_to_https (url.value),
-                                    Gdk.CURRENT_TIME);
-          } catch (Error e) {
-            var message = "Failed to open url '%s'".printf(url.value);
-
-            // Notify the user
-            var notification = new InAppNotification (message);
-            notification.show ();
-            window.add_notification (notification);
-
-            // Print details on stdout
-            debug (message + ": " + e.message);
-          }
-        });
-        add_row_with_label (_("Website"), url.value, button);
-      }
+  private void add_urls (Persona persona, string property) {
+    unowned var url_details = persona as UrlDetails;
+    if (url_details == null)
+      return;
+
+    foreach (var url in url_details.urls) {
+      if (url.value == "")
+        continue;
+
+      var row = new ContactSheetRow (property, url.value);
+
+      var button = row.add_button ("external-link-symbolic");
+      button.tooltip_text = _("Visit website");
+      button.clicked.connect (() => {
+        unowned var window = button.get_root () as MainWindow;
+        if (window == null)
+          return;
+
+        // FIXME: use show_uri_full so we can show errors
+        Gtk.show_uri (window,
+                      fallback_to_https (url.value),
+                      Gdk.CURRENT_TIME);
+      });
+
+      this.attach_row (row);
     }
   }
 
@@ -300,33 +319,52 @@ public class Contacts.ContactSheet : Gtk.Grid {
     return url;
   }
 
-  private void add_nickname (Persona persona) {
-    var name_details = persona as NameDetails;
-    if (name_details != null && is_set (name_details.nickname))
-      add_row_with_label (_("Nickname"), name_details.nickname);
+  private void add_nickname (Persona persona, string property) {
+    unowned var name_details = persona as NameDetails;
+    if (name_details == null || name_details.nickname == "")
+      return;
+
+    var row = new ContactSheetRow (property, name_details.nickname);
+    this.attach_row (row);
   }
 
-  private void add_birthday (Persona persona) {
-    var birthday_details = persona as BirthdayDetails;
-    if (birthday_details != null && birthday_details.birthday != null)
-      add_row_with_label (_("Birthday"), birthday_details.birthday.to_local ().format ("%x"));
+  private void add_birthday (Persona persona, string property) {
+    unowned var birthday_details = persona as BirthdayDetails;
+    if (birthday_details == null || birthday_details.birthday == null)
+      return;
+
+    var birthday_str = birthday_details.birthday.to_local ().format ("%x");
+    var row = new ContactSheetRow (property, birthday_str);
+    this.attach_row (row);
   }
 
-  private void add_notes (Persona persona) {
-    var note_details = persona as NoteDetails;
-    if (note_details != null) {
-      foreach (var note in note_details.notes)
-        add_row_with_label (_("Note"), note.value);
+  private void add_notes (Persona persona, string property) {
+    unowned var note_details = persona as NoteDetails;
+    if (note_details == null)
+      return;
+
+    foreach (var note in note_details.notes) {
+      if (note.value == "")
+        continue;
+
+      var row = new ContactSheetRow (property, note.value);
+      this.attach_row (row);
     }
   }
 
-  private void add_postal_addresses (Persona persona) {
-    var addr_details = persona as PostalAddressDetails;
-    if (addr_details != null) {
-      foreach (var addr in addr_details.postal_addresses) {
-        var all_strs = string.joinv ("\n", Utils.format_address (addr.value));
-        add_row_with_label (TypeSet.general.format_type (addr), all_strs);
-      }
+  private void add_postal_addresses (Persona persona, string property) {
+    unowned var addr_details = persona as PostalAddressDetails;
+    if (addr_details == null)
+      return;
+
+    foreach (var addr in addr_details.postal_addresses) {
+      if (addr.value.is_empty ())
+        continue;
+
+      var row = new ContactSheetRow (property,
+                                     string.joinv ("\n", Utils.format_address (addr.value)),
+                                     TypeSet.general.format_type (addr));
+      this.attach_row (row);
     }
   }
 }
diff --git a/src/contacts-crop-cheese-dialog.vala b/src/contacts-crop-dialog.vala
similarity index 75%
rename from src/contacts-crop-cheese-dialog.vala
rename to src/contacts-crop-dialog.vala
index 1904d8a4..a85c53e0 100644
--- a/src/contacts-crop-cheese-dialog.vala
+++ b/src/contacts-crop-dialog.vala
@@ -15,12 +15,14 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-crop-cheese-dialog.ui")]
-public class Contacts.CropCheeseDialog : Gtk.Window {
+// XXX document and make gtkdialog
+[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-crop-dialog.ui")]
+public class Contacts.CropDialog : Gtk.Window {
+
   [GtkChild]
   private unowned Gtk.Stack stack;
-  [GtkChild]
-  private unowned Gtk.Button take_another_button;
+  // [GtkChild]
+  // private unowned Gtk.Button take_another_button;
 
   private Cc.CropArea crop_area;
   private const string STACK_NAME_CROP = "crop";
@@ -33,33 +35,35 @@ public class Contacts.CropCheeseDialog : Gtk.Window {
 
   public signal void picture_selected (Gdk.Pixbuf buf);
 
-  public CropCheeseDialog.for_cheese (Gtk.Window parent) {
-#if HAVE_CHEESE
-    setup_widget (parent);
-    this.flash = new Cheese.Flash (this);
-    this.cheese = new Cheese.Widget ();
-    this.cheese.show ();
-    this.stack.add_named (this.cheese, STACK_NAME_CHEESE);
-    this.stack.set_visible_child_name (STACK_NAME_CHEESE);
-#endif
+  construct {
+    this.crop_area = new Cc.CropArea ();
+    this.crop_area.set_vexpand (true);
+    this.crop_area.set_hexpand (true);
+    this.crop_area.set_min_size (48, 48);
+    this.crop_area.set_constrain_aspect (true);
+    this.stack.add_named (this.crop_area, STACK_NAME_CROP);
   }
 
-  public CropCheeseDialog.for_crop (Gtk.Window parent, Gdk.Pixbuf pixbuf) {
-    setup_widget (parent);
-    this.take_another_button.visible = false;
+  public CropDialog.for_pixbuf (Gdk.Pixbuf pixbuf,
+                                Gtk.Window? parent = null) {
+    this.set_transient_for (parent);
+
+    // this.take_another_button.visible = false;
     this.crop_area.set_picture (pixbuf);
   }
 
-  /* this function is called from both constructors */
-  private void setup_widget (Gtk.Window parent) {
+  public CropDialog.for_camera_portal (Xdp.Portal portal,
+                                       Gtk.Window? parent = null) {
     this.set_transient_for (parent);
 
-    this.crop_area = new Cc.CropArea ();
-    this.crop_area.set_vexpand (true);
-    this.crop_area.set_hexpand (true);
-    this.crop_area.set_min_size (48, 48);
-    this.crop_area.set_constrain_aspect (true);
-    this.stack.add_named (this.crop_area, STACK_NAME_CROP);
+    // XXX use a pipewire fd and a GStreamer pipeline
+#if HAVE_CHEESE
+    this.flash = new Cheese.Flash (this);
+    this.cheese = new Cheese.Widget ();
+    this.cheese.show ();
+    this.stack.add_named (this.cheese, STACK_NAME_CHEESE);
+    this.stack.set_visible_child_name (STACK_NAME_CHEESE);
+#endif
   }
 
   [GtkCallback]
@@ -79,9 +83,9 @@ public class Contacts.CropCheeseDialog : Gtk.Window {
 #if HAVE_CHEESE
     var camera = this.cheese.get_camera () as Cheese.Camera;
     this.flash.fire ();
-    camera.photo_taken.connect ( (pix) => {
-        this.stack.set_visible_child_name (STACK_NAME_CROP);
-        this.crop_area.set_picture(pix);
+    camera.photo_taken.connect ((pix) => {
+      this.stack.set_visible_child_name (STACK_NAME_CROP);
+      this.crop_area.set_picture (pix);
     });
 
     if (!camera.take_photo_pixbuf ()) {
@@ -94,11 +98,10 @@ public class Contacts.CropCheeseDialog : Gtk.Window {
   [GtkCallback]
   private void on_done_clicked (Gtk.Button button) {
     picture_selected (this.crop_area.get_picture ());
-    destroy();
+    destroy ();
   }
 
-  [GtkCallback]
-  private void on_destroy () {
+  public override bool close_request () {
 #if HAVE_CHEESE
     /* Ensure the Vala garbage collector disposes of the Cheese widget.
      * This prevents the 'Device or resource busy' warnings, see:
@@ -106,6 +109,7 @@ public class Contacts.CropCheeseDialog : Gtk.Window {
      */
     this.cheese = null;
 #endif
-  }
 
+    return base.close_request ();
+  }
 }
diff --git a/src/contacts-delete-operation.vala b/src/contacts-delete-operation.vala
new file mode 100644
index 00000000..022fff5e
--- /dev/null
+++ b/src/contacts-delete-operation.vala
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.DeleteOperation : Object, Operation {
+
+  private Gee.List<Individual> individuals;
+
+  // We don't support reversing a removal. What we do instead, is put a timeout
+  // before actually executing this operation so the user has time to change
+  // their mind.
+  public bool reversable { get { return false; } }
+
+  private string _description;
+  public string description { owned get { return this._description; } }
+
+  public DeleteOperation (Gee.List<Individual> individuals) {
+    this.individuals = individuals;
+    this._description = ngettext ("Deleting %d contact",
+                                  "Deleting %d contacts", individuals.size)
+                        .printf (individuals.size);
+  }
+
+  /**
+   * Link individuals
+   */
+  public async void execute () throws GLib.Error {
+    foreach (var indiv in this.individuals) {
+      foreach (var persona in indiv.personas) {
+        // TODO: make sure it is actually removed
+        yield persona.store.remove_persona (persona);
+      }
+    }
+  }
+
+  // See comments near the reversable property
+  protected async void _undo () throws GLib.Error {
+    throw new GLib.IOError.NOT_SUPPORTED ("Undoing not supported");
+  }
+}
diff --git a/src/contacts-editor-persona.vala b/src/contacts-editor-persona.vala
index f0888a07..41869ce7 100644
--- a/src/contacts-editor-persona.vala
+++ b/src/contacts-editor-persona.vala
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2019 Purism SPC
  * Author: Julian Sparber <julian sparber puri sm>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -37,19 +38,21 @@ public class Contacts.EditorPersona : Gtk.Box {
     "notes"
   };
 
-  private Folks.Persona persona;
-  private Gtk.Box header;
-  private Gtk.ListBox content;
+  private unowned Folks.Persona persona;
+  private unowned Gtk.Box header;
+  private unowned Gtk.Box content;
 
-  private Folks.IndividualAggregator aggregator;
+  private unowned Folks.IndividualAggregator aggregator;
 
   construct {
-    this.header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
-    add (this.header);
+    var _header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+    this.append (_header);
+    this.header = _header;
 
-    this.content = new Gtk.ListBox ();
-    this.content.get_style_context ().add_class ("content");
-    add (this.content);
+    var listbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+    this.content = listbox;
+    this.content.add_css_class ("boxed-list");
+    this.append (this.content);
   }
 
   public EditorPersona (Persona persona, IndividualAggregator aggregator) {
@@ -57,7 +60,7 @@ public class Contacts.EditorPersona : Gtk.Box {
     this.persona = persona;
     this.aggregator = aggregator;
     create_label ();
-    // TODO: implement the possibility of changing the addressbook of a persona
+    // TODO: implement the possibility f changing the addressbook of a persona
 
     // Add most important properites
     foreach (var property in PROPERTIES) {
@@ -66,36 +69,35 @@ public class Contacts.EditorPersona : Gtk.Box {
       foreach (var row in rows) {
         row.show_with_animation (false);
         connect_row (row);
-        this.content.add (row);
+        this.content.append (row);
       }
     }
-    // Add a row with a button to show all properties
-    Gtk.ListBoxRow show_all_row = new Gtk.ListBoxRow ();
-    show_all_row.set_selectable (false);
-    // Add less important property when the show_more button is clicked
-    this.content.row_activated.connect ((current_row) => {
-      if (current_row == show_all_row) {
-        foreach (var property in OTHER_PROPERTIES) {
-          debug ("Create property entry for %s", property);
-          var rows = new EditorProperty (persona, property);
-          foreach (var row in rows) {
-            connect_row (row);
-            this.content.add (row);
-            row.show_with_animation ();
-          }
+
+    // Add less important properties when the show_more button is clicked
+    var show_more_button = new Gtk.Button ();
+    var show_more_content = new Adw.ButtonContent ();
+    show_more_content.icon_name = "view-more-symbolic";
+    show_more_content.label = _("Show More");
+    show_more_button.set_child (show_more_content);
+    show_more_button.halign = Gtk.Align.CENTER;
+    show_more_button.add_css_class ("flat");
+    show_more_button.clicked.connect ((current_row) => {
+      foreach (unowned string property in OTHER_PROPERTIES) {
+        debug ("Create property entry for %s", property);
+        var rows = new EditorProperty (persona, property);
+        foreach (var row in rows) {
+          connect_row (row);
+          this.content.append (row);
+          row.show_with_animation ();
         }
-        show_all_row.destroy ();
       }
+      this.content.remove (show_more_button);
     });
-    Gtk.Image show_all = new Gtk.Image.from_icon_name ("view-more-symbolic",
-                                                       Gtk.IconSize.BUTTON);
-    show_all.margin = 12;
-    show_all_row.add (show_all);
-    this.content.add (show_all_row);
+    this.content.append (show_more_button);
   }
 
   private void connect_row (EditorPropertyRow row) {
-    row.notify["is-empty"].connect ( () => {
+    row.notify["is-empty"].connect (() => {
       var empty_rows_count = this.count_empty_rows (row.ptype);
       if (row.is_empty) {
         // destroy all rows of our type which is not us
@@ -105,9 +107,8 @@ public class Contacts.EditorPersona : Gtk.Box {
         // We are sure that we only created one new row
         var new_rows = new EditorProperty (persona, row.ptype, true);
         if (new_rows.size > 0) {
-          this.content.insert (new_rows[0], row.get_index () + 1);
+          this.content.insert_child_after (new_rows[0], row);
           connect_row (new_rows[0]);
-          new_rows[0].show_with_animation ();
         } else {
           debug ("Couldn't add new row with type %s", row.ptype);
         }
@@ -117,8 +118,10 @@ public class Contacts.EditorPersona : Gtk.Box {
 
   private uint count_empty_rows (string type) {
     uint count = 0;
-    foreach (var row in this.content.get_children ()) {
-      var prop = (row as EditorPropertyRow);
+    for (unowned Gtk.Widget? child = this.content.get_first_child ();
+         child != null;
+         child = child.get_next_sibling ()) {
+      unowned var prop = (child as EditorPropertyRow);
       if (prop != null && !prop.is_removed && prop.is_empty && prop.ptype == type) {
         count++;
       }
@@ -126,20 +129,23 @@ public class Contacts.EditorPersona : Gtk.Box {
     return count;
   }
 
-  private void destroy_empty_rows (Gtk.ListBoxRow current_row, string type) {
-    foreach (var row in this.content.get_children ()) {
-      if (current_row != row) {
-        var prop = (row as EditorPropertyRow);
-        if (prop != null && !prop.is_removed && prop.is_empty && prop.ptype == type) {
-          prop.remove ();
-        }
+  private void destroy_empty_rows (Gtk.Widget current_row, string type) {
+    for (unowned Gtk.Widget? child = this.content.get_first_child ();
+         child != null;
+         child = child.get_next_sibling ()) {
+      if (current_row == child)
+        continue;
+
+      unowned var prop = (child as EditorPropertyRow);
+      if (prop != null && !prop.is_removed && prop.is_empty && prop.ptype == type) {
+        prop.remove ();
       }
     }
   }
 
   private void create_label () {
     string title = "";
-    FakePersona fake_persona = this.persona as FakePersona;
+    unowned var fake_persona = this.persona as FakePersona;
     if (fake_persona != null && fake_persona.real_persona != null) {
       title = fake_persona.real_persona.store.display_name;
     } else {
@@ -147,7 +153,7 @@ public class Contacts.EditorPersona : Gtk.Box {
     }
 
     Gtk.Label addressbook = new Gtk.Label (title);
-    addressbook.get_style_context ().add_class ("heading");
-    this.header.pack_start (addressbook, false, false, 0);
+    addressbook.add_css_class ("heading");
+    this.header.append (addressbook);
   }
 }
diff --git a/src/contacts-editor-property.vala b/src/contacts-editor-property.vala
index d03cde3b..e0c2de0f 100644
--- a/src/contacts-editor-property.vala
+++ b/src/contacts-editor-property.vala
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2019 Purism SPC
  * Author: Julian Sparber <julian sparber puri sm>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,71 +20,65 @@
 using Folks;
 
 public class Contacts.BirthdayEditor : Gtk.Dialog {
-  private Gtk.SpinButton day_spin;
-  private Gtk.ComboBoxText month_combo;
-  private Gtk.SpinButton year_spin;
-  public bool is_set { get; set; default = false; }
 
-  public signal void changed ();
-  delegate void AdjustingDateFn ();
+  private unowned Gtk.SpinButton day_spin;
+  private unowned Gtk.ComboBoxText month_combo;
+  private unowned Gtk.SpinButton year_spin;
 
-  public GLib.DateTime get_birthday () {
-    return new GLib.DateTime.local (year_spin.get_value_as_int (),
-                                    month_combo.get_active () + 1,
-                                    day_spin.get_value_as_int (),
-                                    0, 0, 0).to_utc ();
-  }
+  public bool is_set { get; set; default = false; }
 
-  public BirthdayEditor (Gtk.Window window, DateTime birthday) {
-    Object (transient_for: window, use_header_bar: 1);
-    day_spin = new Gtk.SpinButton.with_range (1.0, 31.0, 1.0);
-    day_spin.set_digits (0);
-    day_spin.numeric = true;
-    day_spin.set_value ((double)birthday.to_local ().get_day_of_month ());
+  public signal void changed ();
 
-    month_combo = new Gtk.ComboBoxText ();
+  construct {
+    // The grid that will contain the Y/M/D fields
+    var grid = new Gtk.Grid ();
+    grid.column_spacing = 12;
+    grid.row_spacing = 12;
+    grid.add_css_class ("contacts-editor-birthday");
+    ((Gtk.Box) this.get_content_area ()).append (grid);
+
+    // Day
+    var d_spin = new Gtk.SpinButton.with_range (1.0, 31.0, 1.0);
+    d_spin.digits = 0;
+    d_spin.numeric = true;
+    this.day_spin = d_spin;
+
+    // Month
+    var m_combo = new Gtk.ComboBoxText ();
     var january = new DateTime.local (1, 1, 1, 1, 1, 1);
     for (int i = 0; i < 12; i++) {
       var month = january.add_months (i);
-      month_combo.append_text (month.format ("%B"));
+      m_combo.append_text (month.format ("%B"));
     }
-    month_combo.set_active (birthday.to_local ().get_month () - 1);
-    month_combo.hexpand = true;
+    m_combo.hexpand = true;
+    this.month_combo = m_combo;
 
-    year_spin = new Gtk.SpinButton.with_range (1800, 3000, 1);
-    year_spin.set_digits (0);
-    year_spin.numeric = true;
-    year_spin.set_value ((double)birthday.to_local ().get_year ());
+    // Year
+    var y_spin = new Gtk.SpinButton.with_range (1800, 3000, 1);
+    y_spin.set_digits (0);
+    y_spin.numeric = true;
+    this.year_spin = y_spin;
 
     // Create grid and labels
-    var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12);
-    var grid = new Gtk.Grid ();
-    grid.set_column_spacing (12);
-    grid.set_row_spacing (12);
-    Gtk.Label day = new Gtk.Label(_("Day"));
+    Gtk.Label day = new Gtk.Label (_("Day"));
     day.set_halign (Gtk.Align.END);
     grid.attach (day, 0, 0);
     grid.attach (day_spin, 1, 0);
-    Gtk.Label month = new Gtk.Label(_("Month"));
+    Gtk.Label month = new Gtk.Label (_("Month"));
     month.set_halign (Gtk.Align.END);
     grid.attach (month, 0, 1);
     grid.attach (month_combo, 1, 1);
-    Gtk.Label year = new Gtk.Label(_("Year"));
+    Gtk.Label year = new Gtk.Label (_("Year"));
     year.set_halign (Gtk.Align.END);
     grid.attach (year, 0, 2);
     grid.attach (year_spin, 1, 2);
-    box.pack_start (grid);
-
-    var content = this.get_content_area ();
-    content.set_valign (Gtk.Align.CENTER);
-    content.add (box);
 
     this.title = _("Change Birthday");
     add_buttons (_("Set"), Gtk.ResponseType.OK,
                  _("Cancel"), Gtk.ResponseType.CANCEL,
                  null);
     var ok_button = this.get_widget_for_response (Gtk.ResponseType.OK);
-    ok_button.get_style_context ().add_class ("suggested-action");
+    ok_button.add_css_class ("suggested-action");
     this.response.connect ((id) => {
       switch (id) {
         case Gtk.ResponseType.OK:
@@ -95,39 +90,48 @@ public class Contacts.BirthdayEditor : Gtk.Dialog {
       }
       this.destroy ();
     });
+  }
 
-    box.margin = 12;
-    box.show_all ();
-
-    AdjustingDateFn fn = () => {
-      int[] month_of_31 = {3, 5, 8, 10};
-      if (month_combo.get_active () in month_of_31) {
-        day_spin.set_range (1, 30);
-      } else if (month_combo.get_active () == 1) {
-        if (year_spin.get_value_as_int () % 400 == 0 ||
-            (year_spin.get_value_as_int () % 4 == 0 &&
-             year_spin.get_value_as_int () % 100 != 0)) {
-          day_spin.set_range (1, 29);
-        } else {
-          day_spin.set_range (1, 28);
-        }
-      } else {
-        day_spin.set_range (1, 31);
-      }
-    };
+  public BirthdayEditor (Gtk.Window window, DateTime birthday) {
+    Object (transient_for: window, use_header_bar: 1);
 
-    /* adjusting day_spin value using selected month/year constraints*/
-    fn ();
+    this.day_spin.set_value ((double) birthday.get_day_of_month ());
+    this.month_combo.set_active (birthday.get_month () - 1);
+    this.year_spin.set_value ((double) birthday.get_year ());
 
+    update_date ();
     month_combo.changed.connect (() => {
-      /* adjusting day_spin value using selected month constraints*/
-      fn ();
+      update_date ();
     });
     year_spin.value_changed.connect (() => {
-      /* adjusting day_spin value using selected year constraints*/
-      fn ();
+      update_date ();
     });
   }
+
+  public GLib.DateTime get_birthday () {
+    return new GLib.DateTime.local (year_spin.get_value_as_int (),
+                                    month_combo.get_active () + 1,
+                                    day_spin.get_value_as_int (),
+                                    0, 0, 0).to_utc ();
+  }
+
+  private void update_date() {
+    const int[] month_of_31 = {3, 5, 8, 10};
+
+    if (this.month_combo.get_active () in month_of_31) {
+      this.day_spin.set_range (1, 30);
+    } else if (this.month_combo.get_active () == 1) {
+      if (this.year_spin.get_value_as_int () % 400 == 0 ||
+          (this.year_spin.get_value_as_int () % 4 == 0 &&
+           this.year_spin.get_value_as_int () % 100 != 0)) {
+        this.day_spin.set_range (1, 29);
+      } else {
+        this.day_spin.set_range (1, 28);
+      }
+    } else {
+      this.day_spin.set_range (1, 31);
+    }
+  }
 }
 
 public class Contacts.AddressEditor : Gtk.Box {
@@ -138,28 +142,31 @@ public class Contacts.AddressEditor : Gtk.Box {
 
   public signal void changed ();
 
-  public AddressEditor (PostalAddressFieldDetails details) {
-    set_hexpand (true);
-    set_orientation (Gtk.Orientation.VERTICAL);
+  construct {
+    this.add_css_class ("contacts-editor-address");
 
+    this.hexpand = true;
+    this.orientation = Gtk.Orientation.VERTICAL;
+  }
+
+  public AddressEditor (PostalAddressFieldDetails details) {
     for (int i = 0; i < entries.length; i++) {
       string postal_part;
       details.value.get (AddressEditor.postal_element_props[i], out postal_part);
 
-      entries[i] = new Gtk.Entry ();
-      entries[i].set_hexpand (true);
-      entries[i].set ("placeholder-text", AddressEditor.postal_element_names[i]);
+      this.entries[i] = new Gtk.Entry ();
+      this.entries[i].hexpand = true;
+      this.entries[i].placeholder_text = AddressEditor.postal_element_names[i];
+      this.entries[i].add_css_class ("flat");
 
       if (postal_part != null)
-        entries[i].set_text (postal_part);
+        this.entries[i].text = postal_part;
 
-      entries[i].get_style_context ().add_class ("contacts-postal-entry");
-      add (entries[i]);
+      append (this.entries[i]);
 
-      var entry = entries[i];
       var prop_name = AddressEditor.postal_element_props[i];
       entries[i].changed.connect (() => {
-        details.value.set (prop_name, entry.get_text ());
+        details.value.set (prop_name, this.entries[i].text);
         changed ();
       });
     }
@@ -173,58 +180,45 @@ public class Contacts.AddressEditor : Gtk.Box {
     }
     return true;
   }
-
-  public override void grab_focus () {
-    entries[0].grab_focus ();
-  }
 }
 
-public class Contacts.EditorPropertyRow : Gtk.ListBoxRow {
+/**
+ * Basic widget to show a single property of a contact (for example an email
+ * address, a birthday, ...). It can show itself using a GtkRevealer animation.
+ *
+ * To edit the value of the property, you should supply a widget and set it as
+ * the main widget.
+ */
+public class Contacts.EditorPropertyRow : Adw.Bin {
+
+  private unowned Gtk.Revealer revealer;
+  private unowned Gtk.ListBox listbox;
+
   public bool is_empty { get; set; default = true; }
   public bool is_removed { get; set; default = false; }
-  public string ptype { get; private set; }
-  public Gtk.Box container;
-  public Gtk.Box header;
-  public Gtk.Revealer revealer;
+  public bool removable { get; set; default = false; }
+
+  /** Internal type name of the property */
+  public string ptype { get; construct; }
 
   construct {
-    this.revealer = new Gtk.Revealer ();
-    //TODO: bind orientation property to available space
-    var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
-    box.set_valign (Gtk.Align.START);
-    box.set_can_focus (false);
-    this.container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
-    this.container.set_can_focus (false);
-    this.header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
-    this.header.set_can_focus (false);
-    box.pack_start (this.header);
-    box.pack_end (this.container);
-    this.set_activatable (false);
-    this.set_selectable (false);
-    this.set_can_focus (false);
-    box.margin = 12;
-    this.revealer.add (box);
-    add (this.revealer);
-    this.get_style_context ().add_class ("editor-property-row");
-    this.revealer.bind_property ("reveal-child", this, "is-removed", BindingFlags.INVERT_BOOLEAN);
+    var _revealer = new Gtk.Revealer ();
+    _revealer.bind_property ("reveal-child", this, "is-removed",
+                             BindingFlags.BIDIRECTIONAL | BindingFlags.INVERT_BOOLEAN);
+    this.child = _revealer;
+    this.revealer = _revealer;
+
+    var list_box = new Gtk.ListBox ();
+    this.listbox = list_box;
+    this.listbox.selection_mode = Gtk.SelectionMode.NONE;
+    this.listbox.activate_on_single_click = true;
+    this.listbox.add_css_class ("boxed-list");
+    this.listbox.add_css_class ("contacts-editor-property");
+    this.revealer.set_child (listbox);
   }
 
   public EditorPropertyRow (string type) {
-    this.ptype = type;
-  }
-
-  // This hides the widget with an animation and then destroys it
-  public new void remove () {
-    this.revealer.set_reveal_child (false);
-    // Remove the separator during the animation to make it look a little better
-    Timeout.add (this.revealer.get_transition_duration ()/2, () => {
-      this.set_header (null);
-      return false;
-    });
-
-    this.revealer.notify["child-revealed"].connect ( () => {
-      this.destroy ();
-    });
+    Object (ptype: type);
   }
 
   public void show_with_animation (bool animate = true) {
@@ -232,121 +226,118 @@ public class Contacts.EditorPropertyRow : Gtk.ListBoxRow {
       var duration = this.revealer.get_transition_duration ();
       this.revealer.set_reveal_child (true);
       this.revealer.set_transition_duration (duration);
-      this.show_all ();
     } else {
-      this.show_all ();
       this.revealer.set_reveal_child (true);
     }
   }
 
-  public void add_base_label (string label) {
-    var title_label = new Gtk.Label (label);
-    title_label.set_hexpand (false);
-    title_label.set_halign (Gtk.Align.START);
-    title_label.margin_end = 6;
-    this.header.pack_start (title_label);
-  }
+  // This hides the widget with an animation and then destroys it
+  public void remove () {
+    debug ("Property %s is removed", this.ptype);
 
-  public void add_base_combo (Gee.Set<AbstractFieldDetails> details_set,
-                              string label,
-                              TypeSet combo_type,
-                              AbstractFieldDetails details) {
-    var title_label = new Gtk.Label (label);
-    title_label.set_halign (Gtk.Align.START);
-    this.header.pack_start (title_label);
-    TypeCombo combo = new TypeCombo (combo_type);
-    combo.set_hexpand (false);
-    combo.set_active_from_field_details (details);
-    this.header.pack_start (combo);
-
-    combo.changed.connect (() => {
-      combo.active_descriptor.save_to_field_details(details);
-      // Workaround: we shouldn't do a manual signal
-      ((FakeHashSet) details_set).changed ();
-      debug ("Property phone changed");
+    this.revealer.set_reveal_child (false);
+
+    // Remove the separator during the animation to make it look a little better
+    Timeout.add (this.revealer.get_transition_duration ()/2, () => {
+      return false;
     });
-  }
 
-  //FIXME: create only one add_base_entry
-  public void add_base_entry_email (Gee.Set<AbstractFieldDetails> details_set,
-                                    EmailFieldDetails details,
-                                    string placeholder) {
-    var value_entry = new Gtk.Entry ();
-    value_entry.set_input_purpose (Gtk.InputPurpose.EMAIL);
-    value_entry.placeholder_text = placeholder;
-    value_entry.set_text (details.value);
-    value_entry.set_hexpand (true);
-    this.container.pack_start (value_entry);
-
-    this.is_empty = details.value == "";
-
-    value_entry.changed.connect (() => {
-      details.value = value_entry.get_text ();
-      // Workaround: we shouldn't do a manual signal
-      ((FakeHashSet) details_set).changed ();
-      debug ("Property email changed");
-      this.is_empty = value_entry.get_text () == "";
+    this.revealer.notify["child-revealed"].connect (() => {
+      this.destroy ();
     });
   }
 
-  public void add_base_entry_phone (Gee.Set<AbstractFieldDetails> details_set,
-                                    PhoneFieldDetails details,
-                                    string placeholder) {
-    var value_entry = new Gtk.Entry ();
-    value_entry.set_input_purpose (Gtk.InputPurpose.PHONE);
-    value_entry.placeholder_text = placeholder;
-    value_entry.set_text (details.value);
-    value_entry.set_hexpand (true);
-    this.container.pack_start (value_entry);
+  /**
+   * Setter for the main widget, which can be used to actually edit the property
+   */
+  public void set_main_widget (Gtk.Widget widget, bool add_icon = true) {
+    var row = new Gtk.ListBoxRow ();
+    row.focusable = false;
+
+    var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+    widget.hexpand = true;
+    row.set_child (box);
+
+    // Start with the icon (if known)
+    if (add_icon) {
+      unowned var icon_name = Utils.get_icon_name_for_property (this.ptype);
+      if (icon_name != null) {
+        var icon = new Gtk.Image.from_icon_name (icon_name);
+        icon.add_css_class ("contacts-property-icon");
+        icon.tooltip_text = Utils.get_display_name_for_property (this.ptype);
+        box.prepend (icon);
+      }
+    }
 
-    this.is_empty = details.value == "";
+    // Set the actual widget
+    // (mimic Adw.ActionRow's "activatable-widget")
+    box.append (widget);
+    this.listbox.row_activated.connect ((activated_row) => {
+      if (row == activated_row)
+        widget.mnemonic_activate (false);
+    });
 
-    value_entry.changed.connect (() => {
-      details.value = value_entry.get_text ();
-      // Workaround: we shouldn't do a manual signal
-      ((FakeHashSet) details_set).changed ();
-      debug ("Property type changed");
+    // Add a delete buton if needed
+    if (this.removable) {
+      var delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
+      delete_button.tooltip_text = _("Delete field");
+      this.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE | 
BindingFlags.INVERT_BOOLEAN);
 
-      this.is_empty = value_entry.get_text () == "";
-    });
+      delete_button.clicked.connect ((b) => { this.remove (); });
+
+      box.append (delete_button);
+    }
+
+    this.listbox.append (row);
   }
 
-  public void add_base_entry_url (Gee.Set<AbstractFieldDetails> details_set,
-                                  UrlFieldDetails details,
-                                  string placeholder) {
-    var value_entry = new Gtk.Entry ();
-    value_entry.placeholder_text = placeholder;
-    value_entry.set_input_purpose (Gtk.InputPurpose.URL);
-    value_entry.set_text (details.value);
-    value_entry.set_hexpand (true);
-    this.container.pack_start (value_entry);
+  /**
+   * Wrapper around set_main_widget() with some extra styling for GtkEntries,
+   * as well as making sure the "is-empty" property is updated.
+   */
+  public Gtk.Entry set_main_entry (string text, string? placeholder = null) {
+    var entry = new Gtk.Entry ();
+    entry.text = text;
+    entry.placeholder_text = placeholder;
+    entry.add_css_class ("flat");
+    entry.add_css_class ("contacts-editor-main-entry");
+    // Set the icon as part of the GtkEntry, to avoid it being outside of the
+    // margin
+    unowned var icon_name = Utils.get_icon_name_for_property (this.ptype);
+    if (icon_name != null) {
+      entry.primary_icon_name = icon_name;
+      entry.primary_icon_tooltip_text = Utils.get_display_name_for_property (this.ptype);
+    }
+    this.set_main_widget (entry, false);
 
-    this.is_empty = details.value == "";
+    this.is_empty = (text == "");
+    entry.changed.connect (() => {
+      this.is_empty = (entry.text == "");
+    });
 
-    value_entry.changed.connect (() => {
-      details.value = value_entry.get_text ();
+    return entry;
+  }
+
+  // Adds an extra row for a type combo, to choose between e.g. "Home" or "Work"
+  public void add_type_combo (Gee.Set<AbstractFieldDetails> details_set,
+                              TypeSet combo_type,
+                              AbstractFieldDetails details) {
+    var row = new TypeComboRow (combo_type);
+    row.title = _("Label");
+    row.set_selected_from_field_details (details);
+    this.listbox.append (row);
+
+    row.notify["selected-item"].connect ((obj, pspec) => {
+      unowned var descr = row.selected_descriptor;
+      descr.save_to_field_details (details);
       // Workaround: we shouldn't do a manual signal
       ((FakeHashSet) details_set).changed ();
-      debug ("Property type changed");
-
-      this.is_empty = value_entry.get_text () == "";
+      debug ("Property phone changed");
     });
   }
 
   public void add_base_delete (Gee.Set<AbstractFieldDetails> details_set,
                                AbstractFieldDetails details) {
-    var delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
-    delete_button.get_accessible ().set_name (_("Delete field"));
-    delete_button.set_valign (Gtk.Align.START);
-    this.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE | 
BindingFlags.INVERT_BOOLEAN);
-    this.container.pack_end (delete_button, false);
-
-
-    delete_button.clicked.connect (() => {
-      debug ("Property removed");
-      this.remove ();
-      details_set.remove (details);
-    });
   }
 }
 
@@ -358,7 +349,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
   public bool writeable { get; private set; default = false; }
 
   public EditorProperty (Persona persona, string property_name, bool only_new = false) {
-    foreach (var s in persona.writeable_properties) {
+    foreach (unowned string s in persona.writeable_properties) {
       if (s == property_name) {
         this.writeable = true;
         break;
@@ -371,7 +362,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
   private void create_for_property (Persona p, string prop_name, bool only_new) {
     switch (prop_name) {
       case "email-addresses":
-        var details = p as EmailDetails;
+        unowned var details = p as EmailDetails;
         if (details != null) {
           var emails = Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
           if (!only_new)
@@ -383,7 +374,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
         }
         break;
       case "phone-numbers":
-        var details = p as PhoneDetails;
+        unowned var details = p as PhoneDetails;
         if (details != null) {
           var phones = Utils.sort_fields<PhoneFieldDetails>(details.phone_numbers);
           if (!only_new)
@@ -395,7 +386,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
         }
         break;
       case "urls":
-        var details = p as UrlDetails;
+        unowned var details = p as UrlDetails;
         if (details != null) {
           var urls = Utils.sort_fields<UrlFieldDetails>(details.urls);
           if (!only_new)
@@ -406,19 +397,19 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
         }
         break;
       case "nickname":
-        var name_details = p as NameDetails;
+        unowned var name_details = p as NameDetails;
         if (name_details != null && name_details.nickname != null && !only_new) {
           add (create_for_nick (name_details));
         }
         break;
       case "birthday":
-        var birthday_details = p as BirthdayDetails;
+        unowned var birthday_details = p as BirthdayDetails;
         if (birthday_details != null && !only_new) {
           add (create_for_birthday (birthday_details));
         }
         break;
       case "notes":
-        var note_details = p as NoteDetails;
+        unowned var note_details = p as NoteDetails;
         if (note_details != null) {
           if (!only_new)
             foreach (var note in note_details.notes) {
@@ -429,7 +420,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
         }
         break;
       case "postal-addresses":
-        var address_details = p as PostalAddressDetails;
+        unowned var address_details = p as PostalAddressDetails;
         if (address_details != null) {
           if (!only_new)
             foreach (var addr in address_details.postal_addresses) {
@@ -442,111 +433,133 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
     }
   }
 
-  private EditorPropertyRow create_for_email (Gee.Set<AbstractFieldDetails> set,
+  private EditorPropertyRow create_for_email (Gee.Set<AbstractFieldDetails> details_set,
                                               EmailFieldDetails? details = null) {
     if (details == null) {
       var parameters = new Gee.HashMultiMap<string, string> ();
       parameters["type"] = "PERSONAL";
       var new_details = new EmailFieldDetails ("", parameters);
-      set.add(new_details);
+      details_set.add (new_details);
       details = new_details;
     }
-    var box = new EditorPropertyRow ("email-addresses");
-    box.add_base_combo (set, _("Email address"), TypeSet.email, details);
-    box.add_base_entry_email (set, details, _("Add email"));
-    box.add_base_delete (set, details);
 
+    var box = new EditorPropertyRow ("email-addresses");
     box.sensitive = this.writeable;
+
+    var entry = box.set_main_entry (details.value, _("Add email"));
+    entry.set_input_purpose (Gtk.InputPurpose.EMAIL);
+    entry.changed.connect (() => {
+      details.value = entry.get_text ();
+      // Workaround: we shouldn't do a manual signal
+      ((FakeHashSet) details_set).changed ();
+      debug ("Property email changed");
+    });
+
+    box.add_type_combo (details_set, TypeSet.email, details);
+    box.add_base_delete (details_set, details);
+
     return box;
   }
 
-  private EditorPropertyRow create_for_phone (Gee.Set<AbstractFieldDetails> set,
+  private EditorPropertyRow create_for_phone (Gee.Set<AbstractFieldDetails> details_set,
                                               PhoneFieldDetails? details = null) {
     if (details == null) {
       var parameters = new Gee.HashMultiMap<string, string> ();
       parameters["type"] = "CELL";
       var new_details = new PhoneFieldDetails ("", parameters);
-      set.add(new_details);
+      details_set.add (new_details);
       details = new_details;
     }
 
     var box = new EditorPropertyRow ("phone-numbers");
-    box.add_base_combo (set, _("Phone number"), TypeSet.phone, details);
-    box.add_base_entry_phone (set, details, _("Add number"));
-    box.add_base_delete (set, details);
-
     box.sensitive = this.writeable;
+
+    var entry = box.set_main_entry (details.value, _("Add phone number"));
+    entry.set_input_purpose (Gtk.InputPurpose.PHONE);
+    entry.changed.connect (() => {
+      details.value = entry.text;
+      // Workaround: we shouldn't do a manual signal
+      ((FakeHashSet) details_set).changed ();
+      debug ("Property type changed");
+    });
+
+    box.add_type_combo (details_set, TypeSet.phone, details);
+    box.add_base_delete (details_set, details);
+
     return box;
   }
 
   // TODO: add support for different types of urls
-  private EditorPropertyRow create_for_url (Gee.Set<AbstractFieldDetails> set,
+  private EditorPropertyRow create_for_url (Gee.Set<AbstractFieldDetails> details_set,
                                             UrlFieldDetails? details = null) {
     if (details == null) {
       var parameters = new Gee.HashMultiMap<string, string> ();
       parameters["type"] = "PERSONAL";
       var new_details = new UrlFieldDetails ("", parameters);
-      set.add(new_details);
+      details_set.add (new_details);
       details = new_details;
     }
 
     var box = new EditorPropertyRow ("urls");
-    box.add_base_label (_("Website"));
-    box.add_base_entry_url (set, details, _("https://example.com";));
-    box.add_base_delete (set, details);
-
     box.sensitive = this.writeable;
+
+    var entry = box.set_main_entry (details.value, _("https://example.com";));
+    entry.set_input_purpose (Gtk.InputPurpose.URL);
+    entry.changed.connect (() => {
+      details.value = entry.get_text ();
+      // Workaround: we shouldn't do a manual signal
+      ((FakeHashSet) details_set).changed ();
+      debug ("Property type changed");
+    });
+
+    box.add_base_delete (details_set, details);
+
     return box;
   }
 
   private EditorPropertyRow create_for_nick (NameDetails details) {
     var box = new EditorPropertyRow ("nickname");
-    box.add_base_label (_("Nickname"));
-
-    var value_entry = new Gtk.Entry ();
-    value_entry.set_text (details.nickname);
-    value_entry.set_hexpand (true);
-    box.container.pack_start (value_entry);
+    box.sensitive = this.writeable;
 
-    value_entry.changed.connect (() => {
-      details.nickname = value_entry.get_text ();
+    var entry = box.set_main_entry (details.nickname, _("Nickname"));
+    entry.set_input_purpose (Gtk.InputPurpose.NAME);
+    entry.changed.connect (() => {
+      details.nickname = entry.text;
       debug ("Nickname changed");
-      box.is_empty = value_entry.get_text () == "";
     });
 
-    box.sensitive = this.writeable;
     return box;
   }
 
-  // TODO: support different types of nodes
+  // TODO: support different types of notes
   private EditorPropertyRow create_for_note (Gee.Set<NoteFieldDetails> details_set,
                                              NoteFieldDetails? details = null) {
     if (details == null) {
       var parameters = new Gee.HashMultiMap<string, string> ();
       parameters["type"] = "PERSONAL";
       var new_details = new NoteFieldDetails ("", parameters);
-      details_set.add(new_details);
+      details_set.add (new_details);
       details = new_details;
     }
     var box = new EditorPropertyRow ("notes");
-    box.add_base_label (_("Note"));
 
-    var sw = new Gtk.ScrolledWindow (null, null);
-    sw.set_shadow_type (Gtk.ShadowType.OUT);
+    var sw = new Gtk.ScrolledWindow ();
+    sw.focusable = false;
+    sw.has_frame = false;
     sw.set_size_request (-1, 100);
-    var value_text = new Gtk.TextView ();
-    value_text.get_buffer ().set_text (details.value);
-    value_text.set_hexpand (true);
-    sw.add (value_text);
-    box.container.pack_start (sw);
+    var textview = new Gtk.TextView ();
+    textview.get_buffer ().set_text (details.value);
+    textview.hexpand = true;
+    sw.set_child (textview);
+    box.set_main_widget (sw);
 
     box.add_base_delete (details_set, details);
 
-    value_text.get_buffer ().changed.connect (() => {
+    textview.get_buffer ().changed.connect (() => {
       Gtk.TextIter start, end;
-      value_text.get_buffer ().get_start_iter (out start);
-      value_text.get_buffer ().get_end_iter (out end);
-      details.value = value_text.get_buffer ().get_text (start, end, true);
+      textview.get_buffer ().get_start_iter (out start);
+      textview.get_buffer ().get_end_iter (out end);
+      details.value = textview.get_buffer ().get_text (start, end, true);
       // Workaround: we shouldn't do a manual signal
       ((FakeHashSet) details_set).changed ();
       debug ("Property changed");
@@ -558,44 +571,41 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
   }
 
   private EditorPropertyRow create_for_birthday (BirthdayDetails? details) {
-    DateTime date;
+    var date = details.birthday ?? new DateTime.now ();
+
     Gtk.Button button;
     if (details.birthday == null) {
-      date = new DateTime.now ();
       button = new Gtk.Button.with_label (_("Set Birthday"));
     } else {
-      date = details.birthday;
       button = new Gtk.Button.with_label (details.birthday.to_local ().format ("%x"));
     }
 
     var box = new EditorPropertyRow ("birthday");
-    box.add_base_label (_("Birthday"));
-
-    box.container.pack_start (button);
+    box.set_main_widget (button);
 
     button.clicked.connect (() => {
-      var parent_window = button.get_toplevel () as Gtk.Window;
+      unowned var parent_window = button.get_root () as Gtk.Window;
       if (parent_window != null) {
         var dialog = new BirthdayEditor (parent_window, date);
 
         dialog.changed.connect (() => {
           if (dialog.is_set) {
             details.birthday = dialog.get_birthday ();
-            button.set_label (details.birthday.to_local ().format ("%x"));
+            button.set_label (details.birthday.format ("%x"));
             box.is_empty = false;
           }
         });
-        dialog.show_all ();
+        dialog.show ();
       }
     });
 
     box.is_empty = details.birthday == null;
 
     var delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
-    delete_button.get_accessible ().set_name (_("Delete field"));
+    delete_button.tooltip_text = _("Delete field");
     delete_button.set_valign (Gtk.Align.START);
     box.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE | 
BindingFlags.INVERT_BOOLEAN);
-    box.container.pack_end (delete_button, false);
+    // box.container.append (delete_button); XXX
 
     delete_button.clicked.connect (() => {
       debug ("Birthday removed");
@@ -613,19 +623,18 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
     if (details == null) {
       var parameters = new Gee.HashMultiMap<string, string> ();
       parameters["type"] = "HOME";
-      var address = new PostalAddress(null, null, null, null, null, null, null, null, null);
+      var address = new PostalAddress (null, null, null, null, null, null, null, null, null);
       var new_details = new PostalAddressFieldDetails (address, parameters);
-      details_set.add(new_details);
+      details_set.add (new_details);
       details = new_details;
     }
     var box = new EditorPropertyRow ("postal-addresses");
-    box.add_base_combo (details_set, _("Address"), TypeSet.general, details);
 
     var value_address = new AddressEditor (details);
-    box.container.pack_start (value_address);
-
+    box.set_main_widget (value_address);
     box.is_empty = value_address.is_empty ();
 
+    box.add_type_combo (details_set, TypeSet.general, details);
     box.add_base_delete (details_set, details);
 
     value_address.changed.connect (() => {
diff --git a/src/contacts-esd-setup.vala b/src/contacts-esd-setup.vala
index b0c58adb..5a958fc6 100644
--- a/src/contacts-esd-setup.vala
+++ b/src/contacts-esd-setup.vala
@@ -23,7 +23,7 @@ extern bool e_trust_prompt_run_for_source_finish (E.Source source, AsyncResult r
 namespace Contacts {
 
 public E.SourceRegistry? eds_source_registry = null;
-private E.CredentialsPrompter? eds_credentials_prompter = null;
+// private E.CredentialsPrompter? eds_credentials_prompter = null;
 
 public bool ensure_eds_accounts (bool allow_interaction) {
   if (eds_source_registry != null)
@@ -39,70 +39,72 @@ public bool ensure_eds_accounts (bool allow_interaction) {
     return false;
   }
 
-  eds_credentials_prompter = new E.CredentialsPrompter (eds_source_registry);
+  // XXX
+  // eds_credentials_prompter = new E.CredentialsPrompter (eds_source_registry);
 
-  if (!allow_interaction)
-      eds_credentials_prompter.set_auto_prompt (false);
+  // if (!allow_interaction)
+  //     eds_credentials_prompter.set_auto_prompt (false);
 
-  var credentials_provider = eds_credentials_prompter.get_provider ();
+  // var credentials_provider = eds_credentials_prompter.get_provider ();
 
   // First disable credentials prompt for all but addressbook sources...
   foreach (var source in eds_source_registry.list_sources (null)) {
-    // Mark for skip also currently disabled sources
-    if (!source.has_extension (E.SOURCE_EXTENSION_ADDRESS_BOOK))
-      eds_credentials_prompter.set_auto_prompt_disabled_for (source, true);
+      // Mark for skip also currently disabled sources
+  //   if (!source.has_extension (E.SOURCE_EXTENSION_ADDRESS_BOOK))
+  //     eds_credentials_prompter.set_auto_prompt_disabled_for (source, true);
   }
 
   // ...then enable credentials prompt for credential source of the addressbook sources,
   //   which can be a collection source.
   foreach (var source in eds_source_registry.list_sources (E.SOURCE_EXTENSION_ADDRESS_BOOK)) {
-    var cred_source = credentials_provider.ref_credentials_source (source);
-    if (cred_source != null && !source.equal (cred_source))
-      eds_credentials_prompter.set_auto_prompt_disabled_for (cred_source, false);
+  //   var cred_source = credentials_provider.ref_credentials_source (source);
+  //   if (cred_source != null && !source.equal (cred_source))
+  //     eds_credentials_prompter.set_auto_prompt_disabled_for (cred_source, false);
   }
 
   // The eds_credentials_prompter responses to REQUIRED and REJECTED reasons,
   // the SSL_FAILED should be handled elsewhere.
   eds_source_registry.credentials_required.connect((src, reason, cert_pem, cert_err, err) => {
-    on_credentials_required.begin (src, reason, cert_pem, cert_err, err);
+      on_credentials_required.begin (src, reason, cert_pem, cert_err, err);
   });
 
-  eds_credentials_prompter.process_awaiting_credentials ();
+  // eds_credentials_prompter.process_awaiting_credentials ();
 
   return true;
 }
 
 private async void on_credentials_required (E.Source source, E.SourceCredentialsReason reason, string 
cert_pem, TlsCertificateFlags cert_errors, Error err) {
-  if (eds_credentials_prompter.get_auto_prompt_disabled_for (source))
-    return;
-
-  if (reason == E.SourceCredentialsReason.ERROR && err != null) {
-    warning ("Failed to authenticate for source \"%s\": %s", source.display_name, err.message);
-    return;
-  }
-
-  if (reason == E.SourceCredentialsReason.SSL_FAILED) {
-    e_trust_prompt_run_for_source (eds_credentials_prompter.get_dialog_parent (),
-        source, cert_pem, cert_errors, (err != null)? err.message : null, true,
-        null, (obj, res) => on_source_trust_prompt_has_run.begin (source, res));
-  }
+//   if (eds_credentials_prompter.get_auto_prompt_disabled_for (source))
+//     return;
+
+   if (reason == E.SourceCredentialsReason.ERROR && err != null) {
+     warning ("Failed to authenticate for source \"%s\": %s",
+              source.display_name, err.message);
+     return;
+   }
+
+   if (reason == E.SourceCredentialsReason.SSL_FAILED) {
+//     e_trust_prompt_run_for_source (eds_credentials_prompter.get_dialog_parent (),
+//         source, cert_pem, cert_errors, (err != null)? err.message : null, true,
+//         null, (obj, res) => on_source_trust_prompt_has_run.begin (source, res));
+   }
 }
 
-private async void on_source_trust_prompt_has_run (E.Source source, AsyncResult res) {
-  try {
-    e_trust_prompt_run_for_source_finish (source, res, null);
-  } catch (Error e) {
-    warning ("Failed to prompt for trust for source \"%s\": %s", source.display_name, e.message);
-    return;
-  }
-
-  try {
-    // Use null credentials to reuse those from the last time.
-    yield source.invoke_authenticate (null, null);
-  } catch (Error e) {
-    warning ("Failed to invoke authenticate() for source \"%s\": %s", source.display_name, e.message);
-  }
-}
+// private async void on_source_trust_prompt_has_run (E.Source source, AsyncResult res) {
+//   try {
+//     e_trust_prompt_run_for_source_finish (source, res, null);
+//   } catch (Error e) {
+//     warning ("Failed to prompt for trust for source \"%s\": %s", source.display_name, e.message);
+//     return;
+//   }
+
+//   try {
+//     // Use null credentials to reuse those from the last time.
+//     yield source.invoke_authenticate (null, null);
+//   } catch (Error e) {
+//     warning ("Failed to invoke authenticate() for source \"%s\": %s", source.display_name, e.message);
+//   }
+// }
 
 public bool has_goa_account () {
   foreach (var source in eds_source_registry.list_sources (E.SOURCE_EXTENSION_GOA)) {
@@ -188,6 +190,6 @@ public Gtk.Image? get_icon_for_goa_account (string goa_id) {
     return null;
   }
 
-  return new Gtk.Image.from_gicon (provider_icon, Gtk.IconSize.DIALOG);
+  return new Gtk.Image.from_gicon (provider_icon);
 }
 }
diff --git a/src/contacts-fake-persona-store.vala b/src/contacts-fake-persona-store.vala
index c6b8ae21..06437b4b 100644
--- a/src/contacts-fake-persona-store.vala
+++ b/src/contacts-fake-persona-store.vala
@@ -557,7 +557,7 @@ public class Contacts.FakeHashSet<T> : Gee.HashSet<T> {
   public override bool add (T element) {
     var res = base.add (element);
     if (res) {
-      added();
+      added ();
       changed ();
     }
     return res;
diff --git a/src/contacts-link-operation.vala b/src/contacts-link-operation.vala
new file mode 100644
index 00000000..9e75841d
--- /dev/null
+++ b/src/contacts-link-operation.vala
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.LinkOperation : Object, Operation {
+
+  private weak Store store;
+
+  private Gee.LinkedList<Individual> individuals;
+  private Gee.HashSet<Gee.HashSet<Persona>> personas_to_link
+      = new Gee.HashSet<Gee.HashSet<Persona>> ();
+
+  private bool finished { get; set; default = false; }
+
+  private bool _reversable = false;
+  public bool reversable { get { return this._reversable; } }
+
+  private string _description;
+  public string description { owned get { return this._description; } }
+
+  public LinkOperation (Store store, Gee.LinkedList<Individual> individuals) {
+    this.store = store;
+    this.individuals = individuals;
+
+    this._description = ngettext ("Linked %d contact",
+                                  "Linked %d contacts", individuals.size)
+                        .printf (individuals.size);
+  }
+
+  /**
+   * Link individuals
+   */
+  public async void execute () throws GLib.Error {
+    var personas_to_link = new Gee.HashSet<Persona> ();
+    foreach (var i in individuals) {
+      var saved_personas = new Gee.HashSet<Persona> ();
+      foreach (var persona in i.personas) {
+        personas_to_link.add (persona);
+        saved_personas.add (persona);
+      }
+      this.personas_to_link.add (saved_personas);
+    }
+
+    yield this.store.aggregator.link_personas (personas_to_link);
+    this._reversable = true;
+    notify_property ("reversable");
+  }
+
+  /**
+   * Undoing means unlinking
+   */
+  public async void _undo () throws GLib.Error {
+    var individual = this.personas_to_link.first_match(() => {return true;})
+      .first_match(() => {return true;}).individual;
+
+    yield store.aggregator.unlink_individual (individual);
+
+    foreach (var personas in personas_to_link) {
+      yield this.store.aggregator.link_personas (personas);
+    }
+    this._reversable = false;
+    notify_property ("reversable");
+  }
+}
diff --git a/src/contacts-link-suggestion-grid.vala b/src/contacts-link-suggestion-grid.vala
index 967ed68f..bff1bec4 100644
--- a/src/contacts-link-suggestion-grid.vala
+++ b/src/contacts-link-suggestion-grid.vala
@@ -24,6 +24,7 @@ using Folks;
  */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-link-suggestion-grid.ui")]
 public class Contacts.LinkSuggestionGrid : Gtk.Grid {
+
   private const int AVATAR_SIZE = 54;
 
   [GtkChild]
@@ -39,13 +40,9 @@ public class Contacts.LinkSuggestionGrid : Gtk.Grid {
   public signal void suggestion_rejected ();
 
   public LinkSuggestionGrid (Individual individual) {
-    get_style_context ().add_class ("contacts-suggestion");
-
     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.attach (image_frame, 0, 0, 1, 2);
 
     this.description_label.xalign = 0;
     this.description_label.label = Contacts.Utils.has_main_persona (individual) ?
@@ -56,7 +53,6 @@ public class Contacts.LinkSuggestionGrid : Gtk.Grid {
 
     var extra_info = find_extra_description (individual);
     if (extra_info != null) {
-      this.extra_info_label.show ();
       this.extra_info_label.label = extra_info;
     }
 
@@ -66,22 +62,22 @@ public class Contacts.LinkSuggestionGrid : Gtk.Grid {
 
   private string? find_extra_description (Individual individual) {
     // First try an email address
-    var emails = individual.email_addresses;
+    unowned 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 = individual.urls;
+    unowned var urls = individual.urls;
     if (!urls.is_empty)
       return Utils.get_first<UrlFieldDetails> (urls).value;
 
     // Try a phone number
-    var phones = individual.phone_numbers;
+    unowned var phones = individual.phone_numbers;
     if (!phones.is_empty)
       return Utils.get_first<PhoneFieldDetails> (phones).value;
 
     // A postal address maybe?
-    var addresses = individual.postal_addresses;
+    unowned 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 26f3ca36..b66e94ce 100644
--- a/src/contacts-linked-personas-dialog.vala
+++ b/src/contacts-linked-personas-dialog.vala
@@ -40,18 +40,15 @@ public class Contacts.LinkedPersonasDialog : Gtk.Dialog {
 
     // loading personas for display
     var personas = Contacts.Utils.get_personas_for_display (individual);
-    bool is_first = true;
-    foreach (var p in personas) {
-      if (is_first) {
-        is_first = false;
-        continue;
-      }
-
+    for (int i = 1; i < personas.get_n_items (); i++) {
+      var p = (Persona) personas.get_item (i);
       var row_grid = new Gtk.Grid ();
 
       var image_frame = new Avatar (AVATAR_SIZE, individual);
       image_frame.set_hexpand (false);
-      image_frame.margin = 6;
+      image_frame.margin_top = 6;
+      image_frame.margin_bottom = 6;
+      image_frame.margin_start = 6;
       image_frame.margin_end = 12;
       row_grid.attach (image_frame, 0, 0, 1, 2);
 
@@ -73,7 +70,7 @@ public class Contacts.LinkedPersonasDialog : Gtk.Dialog {
       var button = new Gtk.Button.with_label (_("Unlink"));
       button.margin_end = 6;
       button.set_valign (Gtk.Align.CENTER);
-      button.get_child ().margin = 1;
+      // button.get_child ().margin = 1; XXX
       row_grid.attach (button, 2, 0, 1, 2);
 
       /* signal */
@@ -81,8 +78,7 @@ public class Contacts.LinkedPersonasDialog : Gtk.Dialog {
         // TODO: handly unlinking
         });
 
-      row_grid.show_all ();
-      this.linked_accounts_view.add (row_grid);
+      this.linked_accounts_view.append (row_grid);
     }
   }
 }
diff --git a/src/contacts-list-pane.vala b/src/contacts-list-pane.vala
index 5439b365..83298d67 100644
--- a/src/contacts-list-pane.vala
+++ b/src/contacts-list-pane.vala
@@ -18,12 +18,12 @@
 using Folks;
 
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-list-pane.ui")]
-public class Contacts.ListPane : Gtk.Frame {
+public class Contacts.ListPane : Adw.Bin {
   private Store store;
 
   [GtkChild]
-  private unowned Gtk.ScrolledWindow contacts_list_container;
-  private ContactList contacts_list;
+  private unowned Gtk.Viewport contacts_list_container;
+  private unowned ContactList contacts_list;
 
   [GtkChild]
   public unowned Gtk.SearchEntry filter_entry;
@@ -45,21 +45,23 @@ public class Contacts.ListPane : Gtk.Frame {
   public signal void delete_contacts (Gee.LinkedList<Individual> individual);
   public signal void contacts_marked (int contacts_marked);
 
-  public ListPane (Settings settings, Store contacts_store) {
+  public ListPane (Gtk.Window window, Settings settings, Store contacts_store) {
     this.store = contacts_store;
     this.notify["state"].connect (on_ui_state_changed);
 
+    this.filter_entry.set_key_capture_widget (window);
+
     // Build the filter query
     string[] filtered_fields = Query.MATCH_FIELDS_NAMES;
     foreach (var field in Query.MATCH_FIELDS_ADDRESSES)
       filtered_fields += field;
     this.filter_query = new SimpleQuery ("", filtered_fields);
 
-
     // Load the ContactsView and connect the necessary signals
-    this.contacts_list = new ContactList (settings, contacts_store, this.filter_query);
+    var contactslist = new ContactList (settings, contacts_store, this.filter_query);
+    this.contacts_list = contactslist;
+    this.contacts_list_container.set_child (this.contacts_list);
     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, individual) => {
         selection_changed (individual);
@@ -73,7 +75,7 @@ public class Contacts.ListPane : Gtk.Frame {
   }
 
   public void undo_deletion () {
-    contacts_list.show_all ();
+    contacts_list.show ();
   }
 
   private void on_ui_state_changed (Object obj, ParamSpec pspec) {
@@ -82,7 +84,7 @@ public class Contacts.ListPane : Gtk.Frame {
         = this.contacts_list.sensitive
         = !this.state.editing ();
 
-    this.actions_bar.visible = (this.state == UiState.SELECTING);
+    this.actions_bar.revealed = (this.state == UiState.SELECTING);
   }
 
   [GtkCallback]
@@ -111,9 +113,4 @@ public class Contacts.ListPane : Gtk.Frame {
   private void on_delete_button_clicked (Gtk.Button delete_button) {
     delete_contacts (this.contacts_list.get_marked_contacts_and_hide ());
   }
-
-  /* Limiting width hack */
-  public override void get_preferred_width (out int minimum_width, out int natural_width) {
-    minimum_width = natural_width = 300;
-  }
 }
diff --git a/src/contacts-main-window.vala b/src/contacts-main-window.vala
index 47c67e47..bb8bfd1d 100644
--- a/src/contacts-main-window.vala
+++ b/src/contacts-main-window.vala
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,49 +19,46 @@
 using Folks;
 
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-main-window.ui")]
-public class Contacts.MainWindow : Hdy.ApplicationWindow {
-
-  private const GLib.ActionEntry[] action_entries = {
-    { "edit-contact",     edit_contact     },
-    { "share-contact",    share_contact    },
-    { "unlink-contact",   unlink_contact   },
-    { "delete-contact",   delete_contact   }
+public class Contacts.MainWindow : Adw.ApplicationWindow {
+
+  private const GLib.ActionEntry[] ACTION_ENTRIES = {
+    { "new-contact", new_contact },
+    { "edit-contact", edit_contact },
+    // { "share-contact", share_contact },
+    { "unlink-contact", unlink_contact },
+    { "delete-contact", delete_contact },
+    { "sort-on", null, "s", "'surname'", sort_on_changed },
+    { "undo-operation", undo_operation_action },
   };
 
   [GtkChild]
-  private unowned Hdy.Leaflet header;
-  [GtkChild]
-  private unowned Hdy.Leaflet content_box;
+  private unowned Adw.Leaflet content_box;
   [GtkChild]
   private unowned Gtk.Revealer back_revealer;
   [GtkChild]
   private unowned Gtk.Stack list_pane_stack;
   [GtkChild]
-  private unowned Gtk.Container contact_pane_container;
+  private unowned Gtk.Overlay contact_pane_container;
+  [GtkChild]
+  private unowned Gtk.Box list_pane_page;
   [GtkChild]
-  private unowned Hdy.HeaderBar left_header;
+  private unowned Gtk.Box contact_pane_page;
   [GtkChild]
-  private unowned Gtk.Separator header_separator;
+  private unowned Adw.HeaderBar left_header;
   [GtkChild]
-  private unowned Hdy.HeaderBar right_header;
+  private unowned Adw.HeaderBar right_header;
   [GtkChild]
-  private unowned Gtk.Overlay notification_overlay;
+  private unowned Adw.ToastOverlay toast_overlay;
   [GtkChild]
   private unowned Gtk.Button select_cancel_button;
   [GtkChild]
   private unowned Gtk.MenuButton hamburger_menu_button;
   [GtkChild]
-  private unowned Gtk.ModelButton sort_on_firstname_button;
-  [GtkChild]
-  private unowned Gtk.ModelButton sort_on_surname_button;
-  [GtkChild]
-  private unowned Gtk.MenuButton contact_menu_button;
+  private unowned Gtk.Box contact_sheet_buttons;
   [GtkChild]
   private unowned Gtk.ToggleButton favorite_button;
   private bool ignore_favorite_button_toggled;
   [GtkChild]
-  private unowned Gtk.Button unlink_button;
-  [GtkChild]
   private unowned Gtk.Button add_button;
   [GtkChild]
   private unowned Gtk.Button cancel_button;
@@ -73,13 +71,14 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
   private ListPane list_pane;
   private ContactPane contact_pane;
 
+  // Actions
+  private SimpleActionGroup actions = new SimpleActionGroup ();
+
   public UiState state { get; set; default = UiState.NORMAL; }
 
   // Window state
   public int window_width { get; set; }
   public int window_height { get; set; }
-  public bool window_maximized { get; set; }
-  public bool window_fullscreen { get; set; }
 
   public Settings settings { get; construct set; }
 
@@ -87,84 +86,41 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     get; construct set;
   }
 
-  construct {
-    SimpleActionGroup actions = new SimpleActionGroup ();
-    actions.add_action_entries (action_entries, this);
-    insert_action_group ("window", actions);
+  // If an unduable operation was recently performed, this will be set
+  public Operation? last_operation = null;
 
-    this.sort_on_firstname_button.clicked.connect (() => {
-      this.settings.sort_on_surname = false;
-      on_sort_changed ();
-    });
-    this.sort_on_surname_button.clicked.connect (() => {
-      this.settings.sort_on_surname = true;
-      on_sort_changed ();
-    });
-    on_sort_changed ();
+  construct {
+    this.actions.add_action_entries (ACTION_ENTRIES, this);
+    this.insert_action_group ("window", this.actions);
 
     this.notify["state"].connect (on_ui_state_changed);
 
-    create_contact_pane ();
-    connect_button_signals ();
-    restore_window_state ();
+    this.create_contact_pane ();
+    this.connect_button_signals ();
+    this.restore_window_state ();
 
     if (Config.PROFILE == "development")
-        get_style_context ().add_class ("devel");
+        this.add_css_class ("devel");
   }
 
   public MainWindow (Settings settings, App app, Store contacts_store) {
     Object (
       application: app,
       settings: settings,
-      show_menubar: false,
-      visible: true,
       store: contacts_store
     );
-  }
 
-  private void on_sort_changed () {
-    this.sort_on_firstname_button.active = !this.settings.sort_on_surname;
-    this.sort_on_surname_button.active = this.settings.sort_on_surname;
+    unowned var sort_key = this.settings.sort_on_surname? "surname" : "firstname";
+    var sort_action = (SimpleAction) this.actions.lookup_action ("sort-on");
+    sort_action.set_state (new Variant.string (sort_key));
   }
 
   private void restore_window_state () {
-    // Load initial values
-    this.window_width = this.settings.window_width;
-    this.window_height = this.settings.window_height;
-    this.window_maximized = this.settings.window_maximized;
-    this.window_fullscreen = this.settings.window_fullscreen;
-
     // Apply them
-    if (this.window_width > 0 && this.window_height > 0)
-      set_default_size (this.window_width, this.window_height);
-    if (this.window_maximized)
-      maximize ();
-    if (this.window_fullscreen)
-      fullscreen ();
-  }
-
-  public override bool window_state_event (Gdk.EventWindowState event) {
-    this.window_maximized = (Gdk.WindowState.MAXIMIZED in event.new_window_state);
-    this.window_fullscreen = (Gdk.WindowState.FULLSCREEN in event.new_window_state);
-
-    return base.window_state_event (event);
-  }
-
-  // Called on window resize. Save window size for the next start.
-  public override void size_allocate (Gtk.Allocation allocation) {
-    base.size_allocate (allocation);
-
-    if (this.window_fullscreen || this.window_maximized)
-      return;
-
-    // Get the size via widget.get_size() instead of the allocation
-    // so that the window isn't ever-expanding (in case of CSD).
-    int width = 0;
-    int height = 0;
-    get_size(out width, out height);
-
-    this.window_width = width;
-    this.window_height = height;
+    if (this.settings.window_width > 0 && this.settings.window_height > 0)
+      set_default_size (this.settings.window_width, this.settings.window_height);
+    this.maximized = this.settings.window_maximized;
+    this.fullscreened = this.settings.window_fullscreen;
   }
 
   private void create_contact_pane () {
@@ -172,10 +128,7 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     this.contact_pane.visible = true;
     this.contact_pane.hexpand = true;
     this.contact_pane.contacts_linked.connect (contact_pane_contacts_linked_cb);
-    this.contact_pane.display_name_changed.connect ((display_name) => {
-      this.right_header.title = display_name;
-    });
-    this.contact_pane_container.add (this.contact_pane);
+    this.contact_pane_container.set_child (this.contact_pane);
   }
 
   /**
@@ -186,30 +139,28 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
   public void show_contact_list () {
     // FIXME: if no contact is loaded per backend, I must place a sign
     // saying "import your contacts/add online account"
-    if (list_pane != null)
+    if (this.list_pane != null)
       return;
 
-    list_pane = new ListPane (this.settings, store);
+    this.list_pane = new ListPane (this, this.settings, store);
     bind_property ("state", this.list_pane, "state", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
-    list_pane.selection_changed.connect (list_pane_selection_changed_cb);
-    list_pane.link_contacts.connect (list_pane_link_contacts_cb);
-    list_pane.delete_contacts.connect (delete_contacts);
-
-    list_pane.contacts_marked.connect ((nr_contacts) => {
-        if (this.state == UiState.SELECTING)
-          this.left_header.title = ngettext ("%d Selected", "%d Selected", nr_contacts)
-                                       .printf (nr_contacts);
-        else
-          this.left_header.title = _("Contacts");
-      });
-
-    list_pane_stack.add (list_pane);
-    list_pane.show ();
-    list_pane_stack.visible_child = list_pane;
+    this.list_pane.selection_changed.connect (list_pane_selection_changed_cb);
+    this.list_pane.link_contacts.connect (list_pane_link_contacts_cb);
+    this.list_pane.delete_contacts.connect (delete_contacts);
+
+    this.list_pane.contacts_marked.connect ((nr_contacts) => {
+      string left_title = _("Contacts");
+      if (this.state == UiState.SELECTING)
+        left_title = ngettext ("%d Selected", "%d Selected", nr_contacts)
+                                     .printf (nr_contacts);
+      this.left_header.title_widget = new Adw.WindowTitle (left_title, "");
+    });
 
-    if (this.contact_pane.individual != null)
-      list_pane.select_contact (this.contact_pane.individual);
+    this.list_pane_stack.add_child (this.list_pane);
+    this.list_pane_stack.visible_child = this.list_pane;
 
+    if (this.contact_pane.individual != null)
+      this.list_pane.select_contact (this.contact_pane.individual);
   }
 
   private void on_ui_state_changed (Object obj, ParamSpec pspec) {
@@ -219,8 +170,7 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
         = (this.state == UiState.NORMAL || this.state == UiState.SHOWING);
 
     // UI when showing a contact
-    this.contact_menu_button.visible
-      = this.favorite_button.visible
+    this.contact_sheet_buttons.visible
       = (this.state == UiState.SHOWING);
 
     // Selecting UI
@@ -228,41 +178,25 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     this.selection_button.visible = !(this.state == UiState.SELECTING || this.state.editing ());
 
     if (this.state != UiState.SELECTING)
-      this.left_header.title = _("Contacts");
+      this.left_header.title_widget = new Adw.WindowTitle (_("Contacts"), "");
 
     // Editing UI
     this.cancel_button.visible
         = this.done_button.visible
+        = this.right_header.show_end_title_buttons
         = this.state.editing ();
+    this.right_header.show_end_title_buttons = !this.state.editing ();
     if (this.state.editing ()) {
-      this.done_button.use_underline = true;
       this.done_button.label = (this.state == UiState.CREATING)? _("_Add") : _("Done");
       // Cast is required because Gtk.Button.set_focus_on_click is deprecated and
       // we have to use Gtk.Widget.set_focus_on_click instead
-      ((Gtk.Widget) this.done_button).set_focus_on_click (true);
+      this.done_button.set_focus_on_click (true);
     }
-    // When selecting or editing, we get special headerbars
-    set_selection_mode (this.state == UiState.SELECTING || this.state.editing ());
 
     // Allow the back gesture when not browsing
-    this.content_box.can_swipe_back = this.state == UiState.NORMAL ||
-                                      this.state == UiState.SHOWING ||
-                                      this.state == UiState.SELECTING;
-  }
-
-  private void set_selection_mode (bool selection_mode) {
-    unowned var left_ctx = this.left_header.get_style_context ();
-    unowned var separator_ctx = this.header_separator.get_style_context ();
-    unowned var right_ctx = this.right_header.get_style_context ();
-    if (selection_mode) {
-      left_ctx.add_class ("selection-mode");
-      separator_ctx.add_class ("selection-mode");
-      right_ctx.add_class ("selection-mode");
-    } else {
-      left_ctx.remove_class ("selection-mode");
-      separator_ctx.remove_class ("selection-mode");
-      right_ctx.remove_class ("selection-mode");
-    }
+    this.content_box.can_navigate_back = this.state == UiState.NORMAL ||
+                                         this.state == UiState.SHOWING ||
+                                         this.state == UiState.SELECTING;
   }
 
   [GtkCallback]
@@ -270,18 +204,15 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     show_list_pane ();
   }
 
-  private void share_contact () {
-    debug ("Share isn't implemented, yet");
-  }
-
-  private void edit_contact () {
+  private void edit_contact (GLib.SimpleAction action, GLib.Variant? parameter) {
     if (this.contact_pane.individual == null)
       return;
 
     this.state = UiState.UPDATING;
 
     unowned var name = this.contact_pane.individual.display_name;
-    this.right_header.title = _("Editing %s").printf (name);
+    var title = _("Editing %s").printf (name);
+    this.right_header.title_widget = new Adw.WindowTitle (title, "");
     this.contact_pane.edit_contact ();
   }
 
@@ -300,35 +231,35 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
   [GtkCallback]
   private void on_selection_button_clicked () {
     this.state = UiState.SELECTING;
-    this.left_header.title = ngettext ("%d Selected", "%d Selected", 0)
-                                       .printf (0);
+    var left_title = ngettext ("%d Selected", "%d Selected", 0) .printf (0);
+    this.left_header.title_widget = new Adw.WindowTitle (left_title, "");
   }
 
-  private void unlink_contact () {
-    var individual = this.contact_pane.individual;
+  private void unlink_contact (GLib.SimpleAction action, GLib.Variant? parameter) {
+    unowned var individual = this.contact_pane.individual;
     if (individual == null)
       return;
 
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
-    var operation = new UnLinkOperation (this.store);
-    operation.execute.begin (individual);
-
-    var b = new Gtk.Button.with_mnemonic (_("_Undo"));
-    var notification = new InAppNotification (_("Contacts unlinked"), b);
+    this.last_operation = new UnlinkOperation (this.store, individual);
+    this.last_operation.execute.begin ((obj, res) => {
+      try {
+        this.last_operation.execute.end (res);
+      } catch (GLib.Error e) {
+        warning ("Error unlinking individuals: %s", e.message);
+      }
+    });
 
-    /* signal handlers */
-    b.clicked.connect ( () => {
-        /* here, we will link the thing in question */
-        operation.undo.begin ();
-        notification.dismiss ();
-      });
+    var toast = new Adw.Toast (this.last_operation.description);
+    toast.set_button_label (_("_Undo"));
+    toast.action_name = "window.undo-operation";
 
-    add_notification (notification);
+    this.toast_overlay.add_toast (toast);
   }
 
-  private void delete_contact () {
+  private void delete_contact (GLib.SimpleAction action, GLib.Variant? parameter) {
     var individual = this.contact_pane.individual;
     if (individual == null)
       return;
@@ -337,6 +268,29 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     delete_contacts (new Gee.ArrayList<Individual>.wrap ({ individual }));
   }
 
+  private void sort_on_changed (SimpleAction action, GLib.Variant? new_state) {
+    unowned var sort_key = new_state.get_string ();
+    this.settings.sort_on_surname = (sort_key == "surname");
+    action.set_state (new_state);
+  }
+
+  private void undo_operation_action (SimpleAction action, GLib.Variant? parameter) {
+    if (this.last_operation == null) {
+      warning ("Undo action was called without anything that can be undone?");
+      return;
+    }
+
+    debug ("Undoing operation '%s'", this.last_operation.description);
+    this.last_operation.undo.begin ((obj, res) => {
+      try {
+        this.last_operation.undo.end (res);
+      } catch (GLib.Error e) {
+        warning ("Couldn't undo operation '%s': %s", this.last_operation.description, e.message);
+      }
+      debug ("Finished undoing operation '%s'", this.last_operation.description);
+    });
+  }
+
   private void stop_editing (bool cancel = false) {
     if (this.state == UiState.CREATING) {
       if (cancel) {
@@ -350,16 +304,7 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
     this.contact_pane.stop_editing (cancel);
     this.list_pane.scroll_to_contact ();
 
-    if (this.contact_pane.individual != null) {
-      this.right_header.title = this.contact_pane.individual.display_name;
-    } else {
-      this.right_header.title = "";
-    }
-  }
-
-  public void add_notification (InAppNotification notification) {
-    this.notification_overlay.add_overlay (notification);
-    notification.show ();
+    this.right_header.title_widget = new Adw.WindowTitle ("", "");
   }
 
   public void set_shown_contact (Individual? i) {
@@ -372,20 +317,17 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
       list_pane.select_contact (i);
 
     // clearing right_header
+    this.right_header.title_widget = new Adw.WindowTitle ("", "");
     if (i != null) {
       this.ignore_favorite_button_toggled = true;
       this.favorite_button.active = i.is_favourite;
       this.ignore_favorite_button_toggled = false;
       this.favorite_button.tooltip_text = (i.is_favourite)? _("Unmark as favorite")
-                                                                     : _("Mark as favorite");
-      this.right_header.title = i.display_name;
-    } else {
-      this.right_header.title = "";
+                                                          : _("Mark as favorite");
     }
   }
 
-  [GtkCallback]
-  public void new_contact () {
+  public void new_contact (GLib.SimpleAction action, GLib.Variant? parameter) {
     if (this.state == UiState.UPDATING || this.state == UiState.CREATING)
       return;
 
@@ -393,7 +335,7 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
 
     this.state = UiState.CREATING;
 
-    this.right_header.title = _("New Contact");
+    this.right_header.title_widget = new Adw.WindowTitle (_("New Contact"), "");
 
     this.contact_pane.new_contact ();
     show_contact_pane ();
@@ -411,7 +353,8 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
 
   [GtkCallback]
   private void on_child_transition_running () {
-    if (!content_box.child_transition_running && content_box.visible_child_name == "list-pane")
+    if (!this.content_box.child_transition_running &&
+         this.content_box.visible_child == this.list_pane_page)
       this.list_pane.select_contact (null);
   }
 
@@ -420,21 +363,21 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
       this.back_revealer.visible =
         this.content_box.folded &&
         !this.cancel_button.visible &&
-        this.header.visible_child == this.right_header;
+        this.content_box.visible_child == this.contact_pane_page;
   }
 
   private void show_list_pane () {
-    content_box.visible_child_name = "list-pane";
+    this.content_box.navigate (Adw.NavigationDirection.BACK);
     update_header ();
   }
 
   private void show_contact_pane () {
-    content_box.visible_child_name = "contact-pane";
+    this.content_box.navigate (Adw.NavigationDirection.FORWARD);
     update_header ();
   }
 
   public void show_search (string query) {
-    list_pane.filter_entry.set_text (query);
+    this.list_pane.filter_entry.set_text (query);
   }
 
   private void connect_button_signals () {
@@ -452,49 +395,25 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
       unowned var individual = this.contact_pane.individual;
       if (individual == null)
         return;
-      this.unlink_button.set_visible (individual.personas.size > 1);
-    });
-  }
-
-  [GtkCallback]
-  bool key_press_event_cb (Gdk.EventKey event) {
-    if ((event.keyval == Gdk.keyval_from_name ("q")) &&
-        ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)) {
-      // Clear the contacts so any changed information is stored
-      this.contact_pane.show_contact (null);
-      destroy ();
-    } else if (((event.keyval == Gdk.Key.s) ||
-                (event.keyval == Gdk.Key.f)) &&
-               ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)) {
-      // Explicitly check if this.list_pane is already initialized,
-      // or we might crash at startup
-      if (this.list_pane != null && this.list_pane.filter_entry != null)
-          Utils.grab_entry_focus_no_select (this.list_pane.filter_entry);
-    } else if (event.length >= 1 &&
-               Gdk.keyval_to_unicode (event.keyval) != 0 &&
-               (event.state & Gdk.ModifierType.CONTROL_MASK) == 0 &&
-               (event.state & Gdk.ModifierType.MOD1_MASK) == 0 &&
-               (event.keyval != Gdk.Key.Escape) &&
-               (event.keyval != Gdk.Key.Tab) &&
-               (event.keyval != Gdk.Key.BackSpace) ) {
-      // Explicitly check if this.list_pane is already initialized,
-      // or we might crash at startup
-      if (this.list_pane != null && this.list_pane.filter_entry != null)
-          Utils.grab_entry_focus_no_select (this.list_pane.filter_entry);
-      propagate_key_event (event);
-    }
 
-    return false;
+      var unlink_action = this.actions.lookup_action ("unlink-contact");
+      ((SimpleAction) unlink_action).set_enabled (individual.personas.size > 1);
+    });
   }
 
-  [GtkCallback]
-  bool delete_event_cb (Gdk.EventAny event) {
+  public override bool close_request () {
     // Clear the contacts so any changed information is stored
     this.contact_pane.show_contact (null);
-    return false;
+
+    this.settings.window_width = this.default_width;
+    this.settings.window_height = this.default_height;
+    this.settings.window_maximized = this.maximized;
+    this.settings.window_fullscreen = this.fullscreened;
+
+    return base.close_request ();
   }
 
-  void list_pane_selection_changed_cb (Individual? new_selection) {
+  private void list_pane_selection_changed_cb (Individual? new_selection) {
     set_shown_contact (new_selection);
     if (this.state != UiState.SELECTING)
       this.state = UiState.SHOWING;
@@ -503,105 +422,67 @@ public class Contacts.MainWindow : Hdy.ApplicationWindow {
       show_contact_pane ();
   }
 
-  void list_pane_link_contacts_cb (Gee.LinkedList<Individual> contact_list) {
+  private void list_pane_link_contacts_cb (Gee.LinkedList<Individual> contact_list) {
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
-    var operation = new LinkOperation (this.store);
-    operation.execute.begin (contact_list);
-
-    string msg = ngettext ("%d contacts linked",
-                           "%d contacts linked",
-                           contact_list.size).printf (contact_list.size);
-
-    var b = new Gtk.Button.with_mnemonic (_("_Undo"));
-    var notification = new InAppNotification (msg, b);
-
-    /* signal handlers */
-    b.clicked.connect ( () => {
-        /* here, we will unlink the thing in question */
-        operation.undo.begin ();
-        notification.dismiss ();
-      });
+    this.last_operation = new LinkOperation (this.store, contact_list);
+    this.last_operation.execute.begin ((obj, res) => {
+      try {
+        this.last_operation.execute.end (res);
+      } catch (GLib.Error e) {
+        warning ("Error linking individuals: %s", e.message);
+      }
+    });
 
-    add_notification (notification);
+    var toast = new Adw.Toast (this.last_operation.description);
+    toast.set_button_label (_("_Undo"));
+    toast.action_name = "window.undo-operation";
+    this.toast_overlay.add_toast (toast);
   }
 
   private void delete_contacts (Gee.List<Individual> individuals) {
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
-    string msg;
-    if (individuals.size == 1)
-      msg = _("Deleted contact %s").printf (individuals[0].display_name);
-    else
-      msg = ngettext ("%d contact deleted", "%d contacts deleted", individuals.size)
-              .printf (individuals.size);
-
-    var b = new Gtk.Button.with_mnemonic (_("_Undo"));
-
-    var notification = new InAppNotification (msg, b);
-
-    // Don't wrap (default), but ellipsize
-    notification.message_label.wrap = false;
-    notification.message_label.max_width_chars = 45;
-    notification.message_label.ellipsize = Pango.EllipsizeMode.END;
+    this.last_operation = new DeleteOperation (individuals);
+    var toast = new Adw.Toast (this.last_operation.description);
+    toast.set_button_label (_("_Undo"));
+    toast.action_name = "window.undo-operation";
 
     // signal handlers
     bool really_delete = true;
-    b.clicked.connect ( () => {
-        really_delete = false;
-        notification.dismiss ();
-
-        /* Reset the contact list */
-        list_pane.undo_deletion ();
-
-        set_shown_contact (individuals[0]);
-        this.state = UiState.SHOWING;
-      });
-    notification.dismissed.connect ( () => {
-        if (really_delete)
-          foreach (var i in individuals)
-            foreach (var p in i.personas) {
-              // TODO: make sure it is acctally removed
-              p.store.remove_persona.begin (p, (obj, res) => {
-                try {
-                  p.store.remove_persona.end (res);
-                } catch (Error e) {
-                  debug ("Coudln't remove persona: %s", e.message);
-                }
-              });
-            }
-      });
-
-    add_notification (notification);
-  }
-
-  void contact_pane_contacts_linked_cb (string? main_contact, string linked_contact, LinkOperation 
operation) {
-    string msg;
-    if (main_contact != null)
-      msg = _("%s linked to %s").printf (main_contact, linked_contact);
-    else
-      msg = _("%s linked to the contact").printf (linked_contact);
-
-    var b = new Gtk.Button.with_mnemonic (_("_Undo"));
-    var notification = new InAppNotification (msg, b);
+      // XXX
+    // b.clicked.connect (() => {
+        // really_delete = false;Chris 🌱️:
+        // toast.dismiss ();
+    // });
+    toast.dismissed.connect (() => {
+        if (really_delete) {
+          this.last_operation.execute.begin ((obj, res) => {
+              try {
+                this.last_operation.execute.end (res);
+              } catch (Error e) {
+                debug ("Coudln't remove persona: %s", e.message);
+              }
+          });
+        } else {
+          /* Reset the contact list */
+          this.list_pane.undo_deletion ();
 
-    b.clicked.connect ( () => {
-        notification.dismiss ();
-        operation.undo.begin ();
-      });
+          set_shown_contact (individuals[0]);
+          this.state = UiState.SHOWING;
+        }
+    });
 
-    add_notification (notification);
+    this.toast_overlay.add_toast (toast);
   }
 
-  // Override the default destroy() to save the window state
-  public override void destroy () {
-    this.settings.window_width = this.window_width;
-    this.settings.window_height = this.window_height;
-    this.settings.window_maximized = this.window_maximized;
-    this.settings.window_fullscreen = this.window_fullscreen;
-
-    base.destroy ();
+  private void contact_pane_contacts_linked_cb (string? main_contact, string linked_contact, LinkOperation 
operation) {
+    this.last_operation = operation;
+    var toast = new Adw.Toast (this.last_operation.description);
+    toast.set_button_label (_("_Undo"));
+    toast.action_name = "window.undo-operation";
+    this.toast_overlay.add_toast (toast);
   }
 }
diff --git a/src/contacts-operation.vala b/src/contacts-operation.vala
new file mode 100644
index 00000000..f77944b4
--- /dev/null
+++ b/src/contacts-operation.vala
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Contacts.Operation is a simple interface to describe actions that can be
+ * executed and possibly undone later on (for example, using a button on an
+ * in-app notification).
+ *
+ * Since some operations might not be able undoable later onwards, there is a
+ * property `reversable` that you should check first before calling undo().
+ */
+public interface Contacts.Operation : Object {
+
+  /**
+   * Whether undo() can be called on this object
+   */
+  public abstract bool reversable { get; }
+
+  /**
+   * A user-facing string that tells us what the operation does
+   */
+  public abstract string description { owned get; }
+
+  /**
+   * This the actual implementation of the operation that a subclass needs to
+   * implement.
+   */
+  public abstract async void execute () throws GLib.Error;
+
+  /**
+   * The is the public API undo. If you want, you can override it still, e.g.
+   * to provide better warnings.
+   */
+  public virtual async void undo () throws GLib.Error {
+    // FIXME: should throw an error instead so we can show something to the user
+    if (!this.reversable) {
+      warning ("Can't undo '%s'", this.description);
+      return;
+    }
+
+    yield this._undo ();
+  }
+
+  /**
+   * This the actual implementation of the undo that a subclass needs to
+   * implement.
+   */
+  protected abstract async void _undo () throws GLib.Error;
+}
diff --git a/src/contacts-setup-window.vala b/src/contacts-setup-window.vala
index 9305b8a9..157d1d6e 100644
--- a/src/contacts-setup-window.vala
+++ b/src/contacts-setup-window.vala
@@ -18,9 +18,10 @@
 using Folks;
 
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-setup-window.ui")]
-public class Contacts.SetupWindow : Hdy.ApplicationWindow {
+public class Contacts.SetupWindow : Adw.ApplicationWindow {
+
   [GtkChild]
-  private unowned Gtk.Box content;
+  private unowned Adw.Clamp clamp;
 
   [GtkChild]
   private unowned Gtk.Button setup_done_button;
@@ -36,8 +37,7 @@ public class Contacts.SetupWindow : Hdy.ApplicationWindow {
     Object (application: app, icon_name: Config.APP_ID);
     this.setup_accounts_list = new AccountsList (store);
     this.setup_accounts_list.hexpand = true;
-    this.setup_accounts_list.show ();
-    this.content.add (this.setup_accounts_list);
+    this.clamp.set_child (this.setup_accounts_list);
 
     // Listen for changes
     store.backend_store.backend_available.connect  ( () => {
@@ -46,14 +46,14 @@ public class Contacts.SetupWindow : Hdy.ApplicationWindow {
 
     ulong id2 = 0;
     id2 = this.setup_accounts_list.account_selected.connect (() => {
-        this.setup_done_button.set_sensitive (true);
+        this.setup_done_button.sensitive = true;
         this.setup_accounts_list.disconnect (id2);
       });
 
     fill_accounts_list (store);
 
     this.setup_done_button.clicked.connect (() => {
-        var selected_store = this.setup_accounts_list.selected_store as Edsf.PersonaStore;
+        unowned var selected_store = this.setup_accounts_list.selected_store as Edsf.PersonaStore;
         setup_done (selected_store);
       });
 
diff --git a/src/contacts-type-combo.vala b/src/contacts-type-combo.vala
index 7104fcf9..3f252f7d 100644
--- a/src/contacts-type-combo.vala
+++ b/src/contacts-type-combo.vala
@@ -18,77 +18,47 @@
 using Folks;
 
 /**
- * The TypeCombo is a widget that fills itself with the types of a certain
+ * The TypeComboRow is a widget that fills itself with the types of a certain
  * category (using {@link Contacts.TypeSet}). For example, it allows the user
  * to choose between "Personal", "Home" and "Work" for email addresses,
  * together with all the custom labels it has encountered since then.
  */
-public class Contacts.TypeCombo : Gtk.ComboBox  {
+public class Contacts.TypeComboRow : Adw.ComboRow  {
 
-  private unowned TypeSet type_set;
-
-  /**
-   * The {@link Contacts.TypeDescriptor} that is currently shown
-   */
-  public TypeDescriptor active_descriptor {
-    get {
-      Gtk.TreeIter iter;
-
-      get_active_iter (out iter);
-      assert (!is_separator (this.model, iter));
-
-      unowned TypeDescriptor descriptor;
-      this.model.get (iter, 1, out descriptor);
-      return descriptor;
-    }
-    set {
-      set_active_iter (value.iter);
-    }
+  public TypeDescriptor selected_descriptor {
+    get { return (TypeDescriptor) this.selected_item; }
   }
 
-  construct {
-    this.valign = Gtk.Align.START;
-    this.halign = Gtk.Align.FILL;
-    this.hexpand = true;
-    this.visible = true;
-
-    var renderer = new Gtk.CellRendererText ();
-    pack_start (renderer, true);
-    set_attributes (renderer, "text", 0);
-
-    set_row_separator_func (is_separator);
+  public TypeSet type_set {
+    get { return (TypeSet) this.model; }
   }
 
   /**
-   * Creates a TypeCombo for the given TypeSet. To set the active value,
-   * use the "current-decsriptor" property, set_active_from_field_details(),
-   * or set_active_from_vcard_type()
+   * Creates a TypeComboRow for the given TypeSet.
    */
-  public TypeCombo (TypeSet type_set) {
-    this.type_set = type_set;
-    this.model = type_set.store;
-  }
-
-  private bool is_separator (Gtk.TreeModel model, Gtk.TreeIter iter) {
-    unowned string? s;
-    model.get (iter, 0, out s);
-    return s == null;
+  public TypeComboRow (TypeSet type_set) {
+    Object (
+      model: type_set,
+      expression: new Gtk.PropertyExpression (typeof (TypeDescriptor), null, "display-name")
+    );
   }
 
   /**
    * Sets the value to the type of the given {@link Folks.AbstractFieldDetails}.
    */
-  public void set_active_from_field_details (AbstractFieldDetails details) {
-    this.active_descriptor = this.type_set.lookup_descriptor_for_field_details (details);
+  public void set_selected_from_field_details (AbstractFieldDetails details) {
+    uint position = 0;
+    this.type_set.lookup_by_field_details (details, out position);
+    this.selected = position;
   }
 
   /**
    * Sets the value to the type that best matches the given vcard type
    * (for example "HOME" or "WORK").
    */
-  public void set_active_from_vcard_type (string type) {
-    Gtk.TreeIter iter;
-    this.type_set.get_iter_for_vcard_type (type, out iter);
-    set_active_iter (iter);
+  public void set_selected_from_vcard_type (string type) {
+    uint position = 0;
+    this.type_set.lookup_by_vcard_type (type, out position);
+    this.selected = position;
   }
 }
diff --git a/src/contacts-typeset.vala b/src/contacts-typeset.vala
index 3208ac47..731f9d30 100644
--- a/src/contacts-typeset.vala
+++ b/src/contacts-typeset.vala
@@ -22,7 +22,7 @@ using Folks;
  * phone number can be both for a personal phone, a work phone or even a fax
  * machine.
  */
-public class Contacts.TypeSet : Object  {
+public class Contacts.TypeSet : Object, GLib.ListModel  {
 
   /** Returns the category of typeset (mostly used for debugging). */
   public string category { get; construct set; }
@@ -34,58 +34,20 @@ public class Contacts.TypeSet : Object  {
   private Gee.List<VcardTypeMapping?> vcard_type_mappings
       = new Gee.ArrayList<VcardTypeMapping?> ();
 
-  // Contains 2 columns:
-  // 1. The type's display name (or null for a separator)
-  // 2. The TypeDescriptor
-  public Gtk.ListStore store { get; private set; }
+  private GenericArray<TypeDescriptor> descriptors = new GenericArray<TypeDescriptor> ();
 
   /**
    * Creates a TypeSet for the given category, e.g. "phones" (used for debugging)
    */
   private TypeSet (string? category) {
     Object (category: category);
-
-    this.store = new Gtk.ListStore (2, typeof (unowned string?), typeof (TypeDescriptor));
-  }
-
-  /**
-   * Returns the {@link Gtk.TreeIter} which corresponds to the type of the
-   * given {@link Folks.AbstractFieldDetails}.
-   */
-  public void get_iter_for_field_details (AbstractFieldDetails detail,
-                                          out Gtk.TreeIter iter) {
-    // Note that we shouldn't have null here, but it's there just to be sure.
-    var d = lookup_descriptor_for_field_details (detail);
-    iter = d.iter;
-  }
-
-  /**
-   * Returns the {@link Gtk.TreeIter} which corresponds the best to the given
-   * vcard type.
-   *
-   * @param type A VCard-like type, such as "HOME" or "CELL".
-   */
-  public void get_iter_for_vcard_type (string type, out Gtk.TreeIter iter) {
-    unowned TypeDescriptor? d = lookup_descriptor_by_vcard_type (type);
-    iter = (d != null)? d.iter : this.other_dummy.iter;
-  }
-
-  /**
-   * Returns the {@link Gtk.TreeIter} which corresponds the best to the given
-   * custom label.
-   */
-  public void get_iter_for_custom_label (string label, out Gtk.TreeIter iter) {
-    var descr = get_descriptor_for_custom_label (label);
-    if (descr == null)
-      descr = create_descriptor_for_custom_label (label);
-    iter = descr.iter;
   }
 
   /**
    * Returns the display name for the type of the given AbstractFieldDetails.
    */
   public unowned string format_type (AbstractFieldDetails detail) {
-    var d = lookup_descriptor_for_field_details (detail);
+    var d = lookup_by_field_details (detail);
     return d.display_name;
   }
 
@@ -93,15 +55,10 @@ public class Contacts.TypeSet : Object  {
    * Adds the TypeDescriptor to the {@link TypeSet}'s store.
    * @param descriptor The TypeDescription to be added
    */
-  private void add_descriptor_to_store (TypeDescriptor descriptor) {
+  private void add_descriptor (TypeDescriptor descriptor) {
     debug ("%s: Adding type %s to store", this.category, descriptor.to_string ());
-
-    if (descriptor.is_custom ())
-      this.store.insert_before (out descriptor.iter, null);
-    else
-      this.store.append (out descriptor.iter);
-
-    store.set (descriptor.iter, 0, descriptor.display_name, 1, descriptor);
+    this.descriptors.add (descriptor);
+    this.items_changed (this.descriptors.length - 1, 0, 1);
   }
 
   /**
@@ -111,60 +68,57 @@ public class Contacts.TypeSet : Object  {
    * @param display_name The translated display name
    * @return The appropriate TypeDescriptor or null if no match was found.
    */
-  public unowned TypeDescriptor? lookup_descriptor_in_store (string display_name) {
-    Gtk.TreeIter iter;
-
-    // Make sure we handle an empty store
-    if (!this.store.get_iter_first (out iter))
-      return null;
-
-    do {
-      unowned TypeDescriptor? type_descr;
-      this.store.get (iter, 1, out type_descr);
-
-      if (display_name.ascii_casecmp (type_descr.display_name) == 0)
-        return type_descr;
-      if (display_name.ascii_casecmp (type_descr.name) == 0)
-        return type_descr;
-    } while (this.store.iter_next (ref iter));
+  public unowned TypeDescriptor? lookup_by_display_name (string display_name,
+                                                         out uint position) {
+    for (int i = 0; i < this.descriptors.length; i++) {
+      unowned var type_descr = this.descriptors[i];
+
+      if (display_name.ascii_casecmp (type_descr.display_name) != 0)
+        continue;
+      if (display_name.ascii_casecmp (type_descr.name) != 0)
+        continue;
+
+      position = i;
+      return type_descr;
+    }
 
     // Nothing was found
+    position = 0;
     return null;
   }
 
   private void add_vcard_mapping (VcardTypeMapping vcard_mapping) {
-    TypeDescriptor? descriptor = lookup_descriptor_in_store (vcard_mapping.name);
+    uint position;
+    var descriptor = lookup_by_display_name (vcard_mapping.name, out position);
     if (descriptor == null) {
       descriptor = new TypeDescriptor.vcard (vcard_mapping.name, vcard_mapping.types);
-      add_descriptor_to_store (descriptor);
+      debug ("%s: Adding VCard type %s to store", this.category, descriptor.to_string ());
+      this.add_descriptor (descriptor);
     }
 
     this.vcard_type_mappings.add (vcard_mapping);
   }
 
-  // Refers to the type of the detail, i.e. "Other" instead of "Personal" or "Work"
-  private void add_type_other () {
-    store.append (out other_dummy.iter);
-    store.set (other_dummy.iter, 0, other_dummy.display_name, 1, other_dummy);
-  }
-
   /**
    * Tries to find the TypeDescriptor matching the given custom label, or null if none.
    */
-  public unowned TypeDescriptor? get_descriptor_for_custom_label (string label) {
+  public TypeDescriptor? lookup_by_custom_label (string label,
+                                                 out uint position) {
     // Check in the current display names
-    unowned TypeDescriptor? descriptor = lookup_descriptor_in_store (label);
+    unowned var descriptor = lookup_by_display_name (label, out position);
     if (descriptor != null)
       return descriptor;
 
     // Try again, but use the vcard types too
-    descriptor = lookup_descriptor_by_vcard_type (label);
+    descriptor = lookup_by_vcard_type (label, out position);
     return descriptor;
   }
 
   private TypeDescriptor create_descriptor_for_custom_label (string label) {
     var new_descriptor = new TypeDescriptor.custom (label);
-    add_descriptor_to_store (new_descriptor);
+    debug ("%s: Adding custom type %s to store",
+           this.category, new_descriptor.to_string ());
+    this.add_descriptor (new_descriptor);
     return new_descriptor;
   }
 
@@ -172,19 +126,26 @@ public class Contacts.TypeSet : Object  {
    * Returns the TypeDescriptor which corresponds the best to the given vcard type.
    * @param str A VCard-like type, such as "HOME" or "CELL".
    */
-  private unowned TypeDescriptor? lookup_descriptor_by_vcard_type (string str) {
+  public unowned TypeDescriptor? lookup_by_vcard_type (string str,
+                                                       out uint position) {
     foreach (VcardTypeMapping? mapping in this.vcard_type_mappings) {
       if (mapping.contains (str))
-        return lookup_descriptor_in_store (mapping.name);
+        return lookup_by_display_name (mapping.name, out position);
     }
 
+    position = 0;
     return null;
   }
 
-  public TypeDescriptor lookup_descriptor_for_field_details (AbstractFieldDetails detail) {
+  /**
+   * Looks up the TypeDescriptor for the given field details. If the descriptor
+   * is not found, it will be created and returned, so this never returns null.
+   */
+  public TypeDescriptor lookup_by_field_details (AbstractFieldDetails detail,
+                                                 out uint position = null) {
     if (detail.parameters.contains (TypeDescriptor.X_GOOGLE_LABEL)) {
       var label = Utils.get_first<string> (detail.parameters[TypeDescriptor.X_GOOGLE_LABEL]);
-      var descriptor = get_descriptor_for_custom_label (label);
+      var descriptor = lookup_by_custom_label (label, out position);
       // Still didn't find it => create it
       if (descriptor == null)
         descriptor = create_descriptor_for_custom_label (label);
@@ -199,12 +160,26 @@ public class Contacts.TypeSet : Object  {
 
     foreach (VcardTypeMapping? d in this.vcard_type_mappings) {
       if (d.matches (types))
-        return lookup_descriptor_in_store (d.name);
+        return lookup_by_display_name (d.name, out position);
     }
 
     return this.other_dummy;
   }
 
+  public GLib.Type get_item_type () {
+    return typeof (TypeDescriptor);
+  }
+
+  public uint get_n_items () {
+    return this.descriptors.length;
+  }
+
+  public GLib.Object? get_item (uint i) {
+    if (i > this.descriptors.length)
+      return null;
+
+    return this.descriptors[i];
+  }
 
   private static TypeSet _general;
   private const VcardTypeMapping[] general_data = {
@@ -218,7 +193,8 @@ public class Contacts.TypeSet : Object  {
         _general = new TypeSet ("General");
         for (int i = 0; i < general_data.length; i++)
           _general.add_vcard_mapping (general_data[i]);
-        _general.add_type_other ();
+
+        _general.add_descriptor (general.other_dummy);
       }
 
       return _general;
@@ -238,7 +214,7 @@ public class Contacts.TypeSet : Object  {
         _email = new TypeSet ("Emails");
         for (int i = 0; i < email_data.length; i++)
           _email.add_vcard_mapping (email_data[i]);
-        _email.add_type_other ();
+        _email.add_descriptor (_email.other_dummy);
       }
 
       return _email;
@@ -275,7 +251,7 @@ public class Contacts.TypeSet : Object  {
         _phone = new TypeSet ("Phones");
         for (int i = 0; i < phone_data.length; i++)
           _phone.add_vcard_mapping (phone_data[i]);
-        _phone.add_type_other ();
+        _phone.add_descriptor (_phone.other_dummy);
       }
 
       return _phone;
diff --git a/src/contacts-unlink-operation.vala b/src/contacts-unlink-operation.vala
new file mode 100644
index 00000000..7b67679b
--- /dev/null
+++ b/src/contacts-unlink-operation.vala
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.UnlinkOperation : Object, Operation {
+
+  private weak Store store;
+
+  private Individual individual;
+
+  private Gee.HashSet<Persona> personas = new Gee.HashSet<Persona> ();
+
+  private bool _reversable = false;
+  public bool reversable { get { return this._reversable; } }
+
+  private string _description;
+  public string description { owned get { return this._description; } }
+
+  public UnlinkOperation (Store store, Individual main) {
+    this.store = store;
+    this.individual = main;
+    this._description = _("Unlinking contacts");
+  }
+
+  /* Remove a personas from individual */
+  public async void execute () throws GLib.Error {
+    foreach (var persona in this.individual.personas)
+      this.personas.add (persona);
+
+    yield store.aggregator.unlink_individual (this.individual);
+    this._reversable = true;
+    notify_property ("reversable");
+  }
+
+  /* Undo the unlinking */
+  public async void _undo () throws GLib.Error {
+    yield this.store.aggregator.link_personas (personas);
+    this._reversable = false;
+    notify_property ("reversable");
+  }
+}
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index 48388765..667c6911 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -65,13 +65,10 @@ namespace Contacts {
 }
 
 namespace Contacts.Utils {
+
   public void compose_mail (string email) {
     var mailto_uri = "mailto:"; + Uri.escape_string (email, "@" , false);
-    try {
-      Gtk.show_uri_on_window (null, mailto_uri, 0);
-    } catch (Error e) {
-      debug ("Couldn't launch URI \"%s\": %s", mailto_uri, e.message);
-    }
+    Gtk.show_uri (null, mailto_uri, 0);
   }
 
 #if HAVE_TELEPATHY
@@ -116,7 +113,7 @@ namespace Contacts.Utils {
     return null;
   }
 
-  public void grab_entry_focus_no_select (Gtk.Entry entry) {
+  public void grab_entry_focus_no_select (Gtk.SearchEntry entry) {
     int start, end;
     if (!entry.get_selection_bounds (out start, out end)) {
       start = end = entry.get_position ();
@@ -189,8 +186,8 @@ namespace Contacts.Utils {
                                         Gtk.MessageType.ERROR,
                                         Gtk.ButtonsType.OK,
                                         "%s", error);
-    dialog.run();
-    dialog.destroy();
+    dialog.response.connect(() => { dialog.destroy(); });
+    dialog.show();
   }
 
   public bool persona_is_main (Persona persona) {
@@ -348,18 +345,23 @@ namespace Contacts.Utils {
     return false;
   }
 
-  public Gee.List<Persona> get_personas_for_display (Individual individual) {
-    CompareDataFunc<Persona> compare_persona_by_store = (a, b) => {
-      unowned var store_a = a.store;
-      unowned var store_b = b.store;
+  public ListModel get_personas_for_display (Individual individual) {
+    var persona_list = new ListStore(typeof(Persona));
+    foreach (var persona in individual.personas)
+      if (persona.store.type_id != "key-file")
+        persona_list.append (persona);
+
+    persona_list.sort ((a, b) => {
+      unowned var store_a = ((Persona) a).store;
+      unowned var store_b = ((Persona) b).store;
 
       // In the same store, sort Google 'other' contacts last
       if (store_a == store_b) {
-        if (!persona_is_google (a))
+        if (!persona_is_google ((Persona) a))
           return 0;
 
-        var a_is_other = persona_is_google_other (a);
-        if (a_is_other != persona_is_google_other (b))
+        var a_is_other = persona_is_google_other ((Persona) a);
+        if (a_is_other != persona_is_google_other ((Persona) b))
           return a_is_other? 1 : -1;
       }
 
@@ -373,14 +375,8 @@ namespace Contacts.Utils {
 
       // Normal case: use alphabetical sorting
       return strcmp (store_a.id, store_b.id);
-    };
-
-    var persona_list = new Gee.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;
   }
 
@@ -567,6 +563,50 @@ namespace Contacts.Utils {
     }
   }
 
+  // A helper struct to keep track on general properties on how each Persona
+  // property should be displayed
+  private struct PropertyDisplayInfo {
+    string property_name;
+    string display_name;
+    string icon_name;
+  }
+
+  private const PropertyDisplayInfo[] display_infos = {
+    { "alias", N_("Alias"), null },
+    { "avatar", N_("Avatar"), "emblem-photos-symbolic" },
+    { "birthday", N_("Birthday"), "birthday-symbolic" },
+    { "calendar-event-id", N_("Calendar event"), "x-office-calendar-symbolic" },
+    { "email-addresses", N_("Email address"), "mail-unread-symbolic" },
+    { "full-name", N_("Full name"), null },
+    { "gender", N_("Gender"), null },
+    { "groups", N_("Group"), null },
+    { "im-addresses", N_("Instant messaging"), "user-available-symbolic" },
+    { "is-favourite", N_("Favourite"), "emblem-favorite-symbolic" },
+    { "local-ids", N_("Local ID"), null },
+    { "nickname", N_("Nickname"), "avatar-default-symbolic" },
+    { "notes", N_("Note"), "note-symbolic" },
+    { "phone-numbers", N_("Phone number"), "phone-symbolic" },
+    { "postal-addresses", N_("Address"), "mark-location-symbolic" },
+    { "roles", N_("Role"), null },
+    { "structured-name", N_("Structured name"), "avatar-default-symbolic" },
+    { "urls", N_("Website"), "web-browser-symbolic" },
+    { "web-service-addresses", N_("Web service"), null },
+  };
+
+  public unowned string get_display_name_for_property (string property_name) {
+    foreach (unowned var info in display_infos)
+      if (info.property_name == property_name)
+        return gettext (info.display_name);
+    return_val_if_reached (null);
+  }
+
+  public unowned string? get_icon_name_for_property (string property_name) {
+    foreach (unowned var info in display_infos)
+      if (info.property_name == property_name)
+        return info.icon_name;
+    return null;
+  }
+
 #if HAVE_TELEPATHY
   public void fetch_contact_info (Individual individual) {
     /* TODO: Ideally Folks should have API for this (#675131) */
diff --git a/src/main.vala b/src/main.vala
index 284a0d43..346c601c 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -23,6 +23,8 @@ main (string[] args) {
   Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
   Intl.textdomain (Config.GETTEXT_PACKAGE);
 
+  GLib.Environment.set_application_name (_("Contacts"));
+
 #if HAVE_CHEESE
   GtkCheese.init (ref args);
 #endif
diff --git a/src/meson.build b/src/meson.build
index d9776384..fc11d335 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,12 +6,16 @@ install_data('org.gnome.Contacts.gschema.xml',
 
 # Common library
 libcontacts_sources = files(
+  'contacts-delete-operation.vala',
   'contacts-esd-setup.vala',
   'contacts-fake-persona-store.vala',
   'contacts-im-service.vala',
+  'contacts-link-operation.vala',
+  'contacts-operation.vala',
   'contacts-store.vala',
   'contacts-typeset.vala',
   'contacts-type-descriptor.vala',
+  'contacts-unlink-operation.vala',
   'contacts-utils.vala',
   'contacts-vcard-type-mapping.vala',
 )
@@ -24,7 +28,6 @@ contacts_vala_args = [
 
 contacts_c_args = [
   '-include', 'config.h',
-  '-DGNOME_DESKTOP_USE_UNSTABLE_API',
   '-DLOCALEDIR="@0@"'.format(locale_dir),
 ]
 
@@ -34,22 +37,16 @@ contacts_deps = [
   gee,
   gio_unix,
   glib,
-  gnome_desktop,
   goa,
-  gtk,
+  gtk4_dep,
+  libadwaita_dep,
   libebook,
   libedataserver,
-  libedataserverui,
-  libhandy,
+  # libedataserverui,
+  libportal_dep,
   math,
 ]
 
-# Add extra stuff given a certain set of options
-if cheese_dep.found() and cheese_gtk_dep.found()
-  contacts_deps += [ cheese_dep, cheese_gtk_dep ]
-  contacts_vala_args += [ '-D', 'HAVE_CHEESE' ]
-endif
-
 if get_option('telepathy')
   contacts_deps += [ folks_telepathy, telepathy_glib ]
   contacts_vala_args += [ '-D', 'HAVE_TELEPATHY' ]
@@ -82,13 +79,11 @@ contacts_vala_sources = files(
   'contacts-contact-list.vala',
   'contacts-contact-pane.vala',
   'contacts-contact-sheet.vala',
-  'contacts-crop-cheese-dialog.vala',
+  'contacts-crop-dialog.vala',
   'contacts-editor-persona.vala',
   'contacts-editor-property.vala',
-  'contacts-in-app-notification.vala',
   'contacts-link-suggestion-grid.vala',
   'contacts-linked-personas-dialog.vala',
-  'contacts-linking.vala',
   'contacts-list-pane.vala',
   'contacts-main-window.vala',
   'contacts-settings.vala',
@@ -102,9 +97,9 @@ contacts_c_sources = [
   'cc-crop-area.c',
 ]
 
-if cheese_dep.found() and cheese_gtk_dep.found()
-  contacts_c_sources += 'cheese-flash.c'
-endif
+# if cheese_dep.found() and cheese_gtk_dep.found()
+#   contacts_c_sources += 'cheese-flash.c'
+# endif
 
 contacts_sources = [
   contacts_c_sources,


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