[geary/wip/714104-refine-account-dialog: 154/180] Add initial replacement account creation pane.



commit fb15b1ca2d6e9f24a945cc66bbe553a3bd98aabb
Author: Michael James Gratton <mike vee net>
Date:   Mon Jul 23 17:46:33 2018 +1000

    Add initial replacement account creation pane.

 po/POTFILES.in                                     |   2 +
 src/client/accounts/accounts-editor-add-pane.vala  | 494 +++++++++++++++++++++
 src/client/accounts/accounts-editor-list-pane.vala | 138 +++++-
 src/client/accounts/accounts-editor-row.vala       |  41 +-
 .../accounts/accounts-editor-servers-pane.vala     |  49 +-
 src/client/application/geary-application.vala      |  15 +-
 src/client/application/geary-controller.vala       | 167 +------
 src/client/components/components-validator.vala    |  94 ++++
 src/client/meson.build                             |   1 +
 src/engine/api/geary-service-information.vala      |  65 +++
 ui/accounts_editor_add_pane.ui                     | 219 +++++++++
 ui/accounts_editor_list_pane.ui                    | 111 ++++-
 ui/geary.css                                       |  14 +-
 ui/org.gnome.Geary.gresource.xml                   |   1 +
 14 files changed, 1173 insertions(+), 238 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6de67a72..733e5c18 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -17,6 +17,7 @@ src/client/accounts/account-dialog.vala
 src/client/accounts/account-dialogs.vala
 src/client/accounts/account-spinner-page.vala
 src/client/accounts/accounts-editor.vala
+src/client/accounts/accounts-editor-add-pane.vala
 src/client/accounts/accounts-editor-edit-pane.vala
 src/client/accounts/accounts-editor-list-pane.vala
 src/client/accounts/accounts-editor-remove-pane.vala
@@ -414,6 +415,7 @@ src/mailer/main.vala
 ui/account_cannot_remove.glade
 ui/account_list.glade
 ui/account_spinner.glade
+ui/accounts_editor_add_pane.ui
 ui/accounts_editor_edit_pane.ui
 ui/accounts_editor_list_pane.ui
 ui/accounts_editor_remove_pane.ui
diff --git a/src/client/accounts/accounts-editor-add-pane.vala 
b/src/client/accounts/accounts-editor-add-pane.vala
new file mode 100644
index 00000000..72523db7
--- /dev/null
+++ b/src/client/accounts/accounts-editor-add-pane.vala
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2018 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * An account editor pane for adding a new account.
+ */
+[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_add_pane.ui")]
+internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
+
+
+    protected weak Accounts.Editor editor { get; set; }
+
+    private Geary.ServiceProvider provider;
+
+    private Manager accounts;
+    private Geary.Engine engine;
+
+
+    [GtkChild]
+    private Gtk.HeaderBar header;
+
+    [GtkChild]
+    private Gtk.Overlay osd_overlay;
+
+    [GtkChild]
+    private Gtk.ListBox details_list;
+
+    [GtkChild]
+    private Gtk.Grid receiving_panel;
+
+    [GtkChild]
+    private Gtk.ListBox receiving_list;
+
+    [GtkChild]
+    private Gtk.Grid sending_panel;
+
+    [GtkChild]
+    private Gtk.ListBox sending_list;
+
+    [GtkChild]
+    private Gtk.Button create_button;
+
+    private NameRow real_name;
+    private EmailRow email = new EmailRow();
+    private string last_valid_email = "";
+
+    private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
+    private LoginRow imap_login = new LoginRow();
+    private PasswordRow imap_password = new PasswordRow();
+
+    private HostnameRow smtp_hostname = new HostnameRow(Geary.Protocol.SMTP);
+    private SmtpAuthRow smtp_auth = new SmtpAuthRow();
+    private LoginRow smtp_login = new LoginRow();
+    private PasswordRow smtp_password = new PasswordRow();
+
+
+    internal EditorAddPane(Editor editor, Geary.ServiceProvider provider) {
+        this.editor = editor;
+        this.provider = provider;
+
+        GearyApplication application = (GearyApplication) editor.application;
+        this.accounts = application.controller.account_manager;
+        this.engine = application.engine;
+
+        this.details_list.set_header_func(Editor.seperator_headers);
+        this.receiving_list.set_header_func(Editor.seperator_headers);
+        this.sending_list.set_header_func(Editor.seperator_headers);
+
+        if (provider != Geary.ServiceProvider.OTHER) {
+            this.details_list.add(
+                new ServiceProviderRow<EditorAddPane>(
+                    provider,
+                    // Translators: Label for adding an email account
+                    // account for a generic IMAP service provider.
+                    _("Other email provider")
+                )
+            );
+            this.receiving_panel.hide();
+            this.sending_panel.hide();
+        }
+
+        this.real_name = new NameRow(get_default_name());
+
+        this.details_list.add(this.real_name);
+        this.details_list.add(this.email);
+
+        this.real_name.validator.notify["state"].connect(on_validated);
+        this.email.validator.notify["state"].connect(on_validated);
+        this.email.value.changed.connect(on_email_changed);
+
+        this.imap_hostname.validator.notify["state"].connect(on_validated);
+        this.imap_login.validator.notify["state"].connect(on_validated);
+        this.imap_password.validator.notify["state"].connect(on_validated);
+
+        this.smtp_hostname.validator.notify["state"].connect(on_validated);
+        this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
+        this.smtp_login.validator.notify["state"].connect(on_validated);
+        this.smtp_password.validator.notify["state"].connect(on_validated);
+
+        if (provider == Geary.ServiceProvider.OTHER) {
+            this.receiving_list.add(this.imap_hostname);
+            this.receiving_list.add(this.imap_login);
+            this.receiving_list.add(this.imap_password);
+
+            this.sending_list.add(this.smtp_hostname);
+            this.sending_list.add(this.smtp_auth);
+        } else {
+            this.details_list.add(this.imap_password);
+        }
+    }
+
+    internal Gtk.HeaderBar get_header() {
+        return this.header;
+    }
+
+    private void add_notification(InAppNotification notification) {
+        this.osd_overlay.add_overlay(notification);
+        notification.show();
+    }
+
+    private string? get_default_name() {
+        string? name = Environment.get_real_name();
+        if (Geary.String.is_empty(name) || name == "Unknown") {
+            name = null;
+        }
+        return name;
+    }
+
+    private async void validate_account(GLib.Cancellable? cancellable) {
+        this.create_button.set_sensitive(false);
+        this.set_sensitive(false);
+
+        bool is_valid = false;
+        Geary.ServiceInformation imap = new_imap_service();
+        Geary.ServiceInformation smtp = new_smtp_service();
+
+        Geary.AccountInformation account =
+            this.accounts.new_orphan_account(this.provider, imap, smtp);
+
+        account.primary_mailbox = new Geary.RFC822.MailboxAddress(
+            this.real_name.value.text.strip(),
+            this.email.value.text.strip()
+        );
+        account.nickname = account.primary_mailbox.address;
+
+        if (this.provider == Geary.ServiceProvider.OTHER) {
+            bool imap_valid = false;
+            bool smtp_valid = false;
+
+            try {
+                yield this.engine.validate_imap(account, cancellable);
+                imap_valid = true;
+            } catch (GLib.Error err) {
+                debug("Error validating IMAP service: %s", err.message);
+                // XXX do something with this
+            }
+
+            // Only validate SMTP if not using IMAP creds, or if the
+            // IMAP creds are good, so we don't check known bad creds
+            if (!smtp.smtp_use_imap_credentials || imap_valid) {
+                //notification.label = _("Checking sending server…");
+
+                try {
+                    yield this.engine.validate_smtp(account, cancellable);
+                    smtp_valid = true;
+                } catch (GLib.Error err) {
+                    debug("Error validating SMTP service: %s", err.message);
+                    // XXX do something with this
+                }
+            }
+
+            is_valid = imap_valid && smtp_valid;
+        } else {
+            //notification.label = _("Checking account…");
+            try {
+                yield this.engine.validate_imap(account, cancellable);
+                is_valid = true;
+            } catch (GLib.Error err) {
+                debug("Error validating provider IMAP: %s", err.message);
+                // XXX do something with this
+            }
+        }
+
+        if (is_valid) {
+            try {
+                yield this.accounts.create_account(account, cancellable);
+            } catch (GLib.Error err) {
+                debug("Failed to create new local account: %s", err.message);
+                // XXX do something with this
+            }
+            this.editor.pop();
+        } else {
+            add_notification(
+                new InAppNotification(
+                    _("Account not added, check the details below")
+                )
+            );
+        }
+
+        this.create_button.set_sensitive(true);
+        this.set_sensitive(true);
+    }
+
+    private LocalServiceInformation new_imap_service() {
+        LocalServiceInformation service =
+           this.accounts.new_libsecret_service(Geary.Protocol.IMAP);
+
+        if (this.provider == Geary.ServiceProvider.OTHER) {
+            service.credentials = new Geary.Credentials(
+                Geary.Credentials.Method.PASSWORD,
+                this.imap_login.value.get_text().strip(),
+                this.imap_password.value.get_text().strip()
+            );
+
+            Components.NetworkAddressValidator host =
+                (Components.NetworkAddressValidator)
+                this.imap_hostname.validator;
+            GLib.NetworkAddress address = host.validated_address;
+            service.host = address.hostname;
+            service.port = (uint16) address.port;
+        } else {
+            service.credentials = new Geary.Credentials(
+                Geary.Credentials.Method.PASSWORD,
+                this.email.value.get_text().strip(),
+                this.imap_password.value.get_text().strip()
+            );
+        }
+
+        return service;
+    }
+
+    private LocalServiceInformation new_smtp_service() {
+        LocalServiceInformation service =
+           this.accounts.new_libsecret_service(Geary.Protocol.SMTP);
+
+        if (this.provider == Geary.ServiceProvider.OTHER) {
+            switch (this.smtp_auth.get_value()) {
+            case Geary.SmtpCredentials.NONE:
+                service.smtp_noauth = true;
+                service.smtp_use_imap_credentials = false;
+                break;
+
+            case Geary.SmtpCredentials.IMAP:
+                service.smtp_noauth = false;
+                service.smtp_use_imap_credentials = true;
+                break;
+
+            case Geary.SmtpCredentials.CUSTOM:
+                service.smtp_noauth = false;
+                service.smtp_use_imap_credentials = false;
+                service.credentials = new Geary.Credentials(
+                    Geary.Credentials.Method.PASSWORD,
+                    this.smtp_login.value.get_text().strip(),
+                    this.smtp_password.value.get_text().strip()
+                );
+                break;
+            }
+
+            Components.NetworkAddressValidator host =
+                (Components.NetworkAddressValidator)
+                this.smtp_hostname.validator;
+            GLib.NetworkAddress address = host.validated_address;
+
+            service.host = address.hostname;
+            service.port = (uint16) address.port;
+        } else {
+            service.smtp_noauth = false;
+            service.smtp_use_imap_credentials = true;
+        }
+
+        return service;
+    }
+
+    private void check_validation() {
+        bool is_valid = true;
+        foreach (Gtk.ListBox list in new Gtk.ListBox[] {
+                this.details_list, this.receiving_list, this.sending_list
+            }) {
+            list.foreach((child) => {
+                    AddPaneRow? validatable = child as AddPaneRow;
+                    if (validatable != null && !validatable.validator.is_valid) {
+                        is_valid = false;
+                    }
+                });
+        }
+        this.create_button.set_sensitive(is_valid);
+    }
+
+    private void on_validated() {
+        check_validation();
+    }
+
+    private void on_email_changed() {
+        string email = "";
+        if (this.email.validator.state == Components.Validator.Validity.VALID) {
+            email = this.email.value.text;
+        }
+
+        if (this.imap_login.value.text == this.last_valid_email) {
+            this.imap_login.value.text = email;
+        }
+        if (this.smtp_login.value.text == this.last_valid_email) {
+            this.smtp_login.value.text = email;
+        }
+
+        this.last_valid_email = email;
+    }
+
+    private void on_smtp_auth_changed() {
+        if (this.smtp_auth.get_value() == Geary.SmtpCredentials.CUSTOM) {
+            this.sending_list.add(this.smtp_login);
+            this.sending_list.add(this.smtp_password);
+        } else if (this.smtp_login.parent != null) {
+            this.sending_list.remove(this.smtp_login);
+            this.sending_list.remove(this.smtp_password);
+        }
+        check_validation();
+    }
+
+    [GtkCallback]
+    private void on_create_button_clicked() {
+        this.validate_account.begin(null);
+    }
+
+    [GtkCallback]
+    private void on_back_button_clicked() {
+        this.editor.pop();
+    }
+
+}
+
+
+private abstract class Accounts.AddPaneRow<Value> :
+    LabelledEditorRow<EditorAddPane,Value> {
+
+
+    internal Components.Validator? validator { get; protected set; }
+
+
+    public AddPaneRow(string label, Value value) {
+        base(label, new Gtk.Entry());
+        this.activatable = false;
+    }
+
+}
+
+
+private abstract class Accounts.EntryRow : AddPaneRow<Gtk.Entry> {
+
+
+    public EntryRow(string label, string? placeholder = null) {
+        base(label, new Gtk.Entry());
+
+        this.value.placeholder_text = placeholder ?? "";
+        this.value.width_chars = 32;
+    }
+
+}
+
+
+private class Accounts.NameRow : EntryRow {
+
+    public NameRow(string default_name) {
+        // Translators: Label for the person's actual name when adding
+        // an account
+        base(_("Your name"));
+        this.validator = new Components.Validator(this.value);
+        if (default_name.strip() != "") {
+            // Set the text after hooking up the validator, so if the
+            // string is non-null it will already be valid
+            this.value.set_text(default_name);
+        }
+    }
+
+}
+
+
+private class Accounts.EmailRow : EntryRow {
+
+
+    public EmailRow() {
+        base(
+            _("Email address"),
+            // Translators: Placeholder for the default sender address
+            // when adding an account
+            _("person example com")
+        );
+        this.value.input_purpose = Gtk.InputPurpose.EMAIL;
+        this.validator = new Components.EmailValidator(this.value);
+    }
+
+}
+
+
+private class Accounts.LoginRow : EntryRow {
+
+    public LoginRow() {
+        // Translators: Label for an IMAP/SMTP service login/user name
+        // when adding an account
+        base(_("Login name"));
+        // Logins are not infrequently the same as the user's email
+        // address
+        this.value.input_purpose = Gtk.InputPurpose.EMAIL;
+        this.validator = new Components.Validator(this.value);
+    }
+
+}
+
+
+private class Accounts.PasswordRow : EntryRow {
+
+
+    public PasswordRow() {
+        base(_("Password"));
+        this.value.visibility = false;
+        this.value.input_purpose = Gtk.InputPurpose.PASSWORD;
+        this.validator = new Components.Validator(this.value);
+    }
+
+}
+
+
+private class Accounts.HostnameRow : EntryRow {
+
+
+    private Geary.Protocol type;
+
+
+    public HostnameRow(Geary.Protocol type) {
+        string label = "";
+        string placeholder = "";
+        switch (type) {
+        case Geary.Protocol.IMAP:
+            // Translators: Label for the IMAP server hostname when
+            // adding an account.
+            label = _("IMAP server");
+            // Translators: Placeholder for the IMAP server hostname
+            // when adding an account.
+            placeholder = _("imap.example.com");
+            break;
+
+        case Geary.Protocol.SMTP:
+            // Translators: Label for the SMTP server hostname when
+            // adding an account.
+            label = _("SMTP server");
+            // Translators: Placeholder for the SMTP server hostname
+            // when adding an account.
+            placeholder = _("smtp.example.com");
+            break;
+        }
+
+        base(label, placeholder);
+        this.type = type;
+
+        this.validator = new Components.NetworkAddressValidator(this.value, 0);
+    }
+
+}
+
+
+private class Accounts.SmtpAuthRow :
+    LabelledEditorRow<EditorAddPane,Gtk.ComboBoxText> {
+
+
+    public SmtpAuthRow() {
+        base(
+            // Translators: Label for SMTP authentication method
+            // (none, use IMAP, custom) when adding a new account
+            _("Login"),
+            new Gtk.ComboBoxText()
+        );
+
+        this.activatable = false;
+
+        this.value.append(Geary.SmtpCredentials.NONE.to_value(), _("No login needed"));
+        this.value.append(Geary.SmtpCredentials.IMAP.to_value(), _("Use IMAP login"));
+        this.value.append(Geary.SmtpCredentials.CUSTOM.to_value(), _("Use different login"));
+
+        this.value.active_id = Geary.SmtpCredentials.IMAP.to_value();
+    }
+
+    public Geary.SmtpCredentials get_value() {
+        try {
+            return Geary.SmtpCredentials.for_value(this.value.active_id);
+        } catch {
+            return Geary.SmtpCredentials.IMAP;
+        }
+    }
+
+}
diff --git a/src/client/accounts/accounts-editor-list-pane.vala 
b/src/client/accounts/accounts-editor-list-pane.vala
index c537fd0e..9136ebcf 100644
--- a/src/client/accounts/accounts-editor-list-pane.vala
+++ b/src/client/accounts/accounts-editor-list-pane.vala
@@ -30,11 +30,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
 
     protected weak Accounts.Editor editor { get; set; }
 
-    private Manager accounts { get; private set; }
+    private Manager accounts;
 
-    private Application.CommandStack commands {
-        get; private set; default = new Application.CommandStack();
-    }
+    private Application.CommandStack commands = new Application.CommandStack();
 
     [GtkChild]
     private Gtk.HeaderBar header;
@@ -42,9 +40,21 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     [GtkChild]
     private Gtk.Overlay osd_overlay;
 
+    [GtkChild]
+    private Gtk.Grid welcome_panel;
+
     [GtkChild]
     private Gtk.ListBox accounts_list;
 
+    [GtkChild]
+    private Gtk.Label add_service_label;
+
+    [GtkChild]
+    private Gtk.ListBox service_list;
+
+    private Gee.Map<Geary.ServiceProvider,EditorAddPane> add_pane_cache =
+        new Gee.HashMap<Geary.ServiceProvider,EditorAddPane>();
+
     private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
         new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
 
@@ -56,12 +66,15 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
 
         this.accounts_list.set_header_func(Editor.seperator_headers);
         this.accounts_list.set_sort_func(ordinal_sort);
-
         foreach (Geary.AccountInformation account in this.accounts.iterable()) {
             add_account(account, this.accounts.get_status(account));
         }
 
-        this.accounts_list.add(new AddRow<EditorServersPane>());
+        this.service_list.set_header_func(Editor.seperator_headers);
+        this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.GMAIL));
+        this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OUTLOOK));
+        this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.YAHOO));
+        this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OTHER));
 
         this.accounts.account_added.connect(on_account_added);
         this.accounts.account_status_changed.connect(on_account_status_changed);
@@ -70,6 +83,8 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         this.commands.executed.connect(on_execute);
         this.commands.undone.connect(on_undo);
         this.commands.redone.connect(on_execute);
+
+        update_welcome_panel();
     }
 
     public override void destroy() {
@@ -81,14 +96,27 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         this.accounts.account_status_changed.disconnect(on_account_status_changed);
         this.accounts.account_removed.disconnect(on_account_removed);
 
+        this.add_pane_cache.clear();
         this.edit_pane_cache.clear();
         base.destroy();
     }
 
-    /** Adds a new account to the list. */
-    internal void add_account(Geary.AccountInformation account,
-                             Manager.Status status) {
-        this.accounts_list.add(new AccountListRow(account, status));
+    internal void show_add_account(Geary.ServiceProvider provider) {
+        EditorAddPane? add_pane = this.add_pane_cache.get(provider);
+        if (add_pane == null) {
+            add_pane = new EditorAddPane(this.editor, provider);
+            this.add_pane_cache.set(provider, add_pane);
+        }
+        this.editor.push(add_pane);
+    }
+
+    internal void show_existing_account(Geary.AccountInformation account) {
+        EditorEditPane? edit_pane = this.edit_pane_cache.get(account);
+        if (edit_pane == null) {
+            edit_pane = new EditorEditPane(this.editor, account);
+            this.edit_pane_cache.set(account, edit_pane);
+        }
+        this.editor.push(edit_pane);
     }
 
     /** Removes an account from the list. */
@@ -118,6 +146,11 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         return this.header;
     }
 
+    private void add_account(Geary.AccountInformation account,
+                             Manager.Status status) {
+        this.accounts_list.add(new AccountListRow(account, status));
+    }
+
     private void add_notification(InAppNotification notification) {
         this.osd_overlay.add_overlay(notification);
         notification.show();
@@ -132,6 +165,22 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         );
     }
 
+    private void update_welcome_panel() {
+        if (this.accounts_list.get_row_at_index(0) == null) {
+            // No accounts are available, so show only the welcome
+            // pane and service list.
+            this.welcome_panel.show();
+            this.accounts_list.hide();
+            this.add_service_label.hide();
+        } else {
+            // There are some accounts available, so show them and
+            // the full add service UI.
+            this.welcome_panel.hide();
+            this.accounts_list.show();
+            this.add_service_label.show();
+        }
+    }
+
     private AccountListRow? get_account_row(Geary.AccountInformation account) {
         AccountListRow? row = null;
         this.accounts_list.foreach((child) => {
@@ -146,6 +195,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     private void on_account_added(Geary.AccountInformation account,
                                   Manager.Status status) {
         add_account(account, status);
+        update_welcome_panel();
     }
 
     private void on_account_status_changed(Geary.AccountInformation account,
@@ -160,6 +210,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         AccountListRow? row = get_account_row(account);
         if (row != null) {
             this.accounts_list.remove(row);
+            update_welcome_panel();
         }
     }
 
@@ -180,16 +231,10 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     }
 
     [GtkCallback]
-    private void on_accounts_list_row_activated(Gtk.ListBoxRow activated) {
-        AccountListRow? row = activated as AccountListRow;
-        if (row != null) {
-            Geary.AccountInformation account = row.account;
-            EditorEditPane? edit_pane = this.edit_pane_cache.get(account);
-            if (edit_pane == null) {
-                edit_pane = new EditorEditPane(this.editor, account);
-                this.edit_pane_cache.set(account, edit_pane);
-            }
-            this.editor.push(edit_pane);
+    private void on_row_activated(Gtk.ListBoxRow row) {
+        EditorRow<EditorListPane>? setting = row as EditorRow<EditorListPane>;
+        if (setting != null) {
+            setting.activated(this);
         }
     }
 
@@ -225,6 +270,10 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
         update(status);
     }
 
+    public override void activated(EditorListPane pane) {
+        pane.show_existing_account(this.account);
+    }
+
     public void update(Manager.Status status) {
         if (status != Manager.Status.UNAVAILABLE) {
             this.unavailable_icon.hide();
@@ -245,7 +294,7 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
         string? details = this.account.service_label;
         switch (account.service_provider) {
         case Geary.ServiceProvider.GMAIL:
-            details = _("GMail");
+            details = _("Gmail");
             break;
 
         case Geary.ServiceProvider.OUTLOOK:
@@ -278,6 +327,53 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
 }
 
 
+private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
+
+
+    internal Geary.ServiceProvider provider;
+
+    private Gtk.Label service_name = new Gtk.Label("");
+    private Gtk.Image next_icon = new Gtk.Image.from_icon_name(
+        "go-next-symbolic", Gtk.IconSize.SMALL_TOOLBAR
+    );
+
+
+    public AddServiceProviderRow(Geary.ServiceProvider provider) {
+        this.provider = provider;
+
+        // Translators: Label for adding a generic email account
+        string? name = _("Other email provider");
+        switch (provider) {
+        case Geary.ServiceProvider.GMAIL:
+            name = _("Gmail");
+            break;
+
+        case Geary.ServiceProvider.OUTLOOK:
+            name = _("Outlook.com");
+            break;
+
+        case Geary.ServiceProvider.YAHOO:
+            name = _("Yahoo");
+            break;
+        }
+        this.service_name.set_text(name);
+        this.service_name.set_hexpand(true);
+        this.service_name.halign = Gtk.Align.START;
+        this.service_name.show();
+
+        this.next_icon.show();
+
+        this.layout.add(this.service_name);
+        this.layout.add(this.next_icon);
+    }
+
+    public override void activated(EditorListPane pane) {
+        pane.show_add_account(this.provider);
+    }
+
+}
+
+
 internal class Accounts.RemoveAccountCommand : Application.Command {
 
 
diff --git a/src/client/accounts/accounts-editor-row.vala b/src/client/accounts/accounts-editor-row.vala
index 38ac0719..2ec4c2f6 100644
--- a/src/client/accounts/accounts-editor-row.vala
+++ b/src/client/accounts/accounts-editor-row.vala
@@ -32,8 +32,8 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
 internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
 
 
-    protected Gtk.Label label { get; private set; default = new Gtk.Label(""); }
-    protected V value;
+    public Gtk.Label label { get; private set; default = new Gtk.Label(""); }
+    public V value { get; private set; }
 
 
     public LabelledEditorRow(string label, V value) {
@@ -81,6 +81,43 @@ internal class Accounts.AddRow<PaneType> : EditorRow<PaneType> {
 }
 
 
+internal class Accounts.ServiceProviderRow<PaneType> :
+    LabelledEditorRow<PaneType,Gtk.Label> {
+
+
+    public ServiceProviderRow(Geary.ServiceProvider provider,
+                              string other_type_label) {
+        string? label = other_type_label;
+        switch (provider) {
+        case Geary.ServiceProvider.GMAIL:
+            label = _("Gmail");
+            break;
+
+        case Geary.ServiceProvider.OUTLOOK:
+            label = _("Outlook.com");
+            break;
+
+        case Geary.ServiceProvider.YAHOO:
+            label = _("Yahoo");
+            break;
+        }
+
+        base(
+            // Translators: Label describes the service provider
+            // hosting the email account, e.g. Gmail, Yahoo, or some
+            // other generic IMAP service.
+            _("Service provider"),
+            new Gtk.Label(label)
+        );
+
+        // Can't change this, so deactivate and dim out
+        set_activatable(false);
+        this.value.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
+    }
+
+}
+
+
 internal abstract class Accounts.AccountRow<PaneType,V> :
     LabelledEditorRow<PaneType,V> {
 
diff --git a/src/client/accounts/accounts-editor-servers-pane.vala 
b/src/client/accounts/accounts-editor-servers-pane.vala
index 8da25e90..aeb9e4fe 100644
--- a/src/client/accounts/accounts-editor-servers-pane.vala
+++ b/src/client/accounts/accounts-editor-servers-pane.vala
@@ -35,7 +35,12 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
         this.account = account;
 
         this.details_list.set_header_func(Editor.seperator_headers);
-        this.details_list.add(new ServiceProviderRow(this.account));
+        this.details_list.add(
+            new ServiceProviderRow<EditorServersPane>(
+                this.account.service_provider,
+                this.account.service_label
+            )
+        );
         // Only add an account provider if it is esoteric enough.
         if (this.account.imap.mediator is GoaMediator) {
             this.details_list.add(new AccountProviderRow(this.account));
@@ -83,48 +88,6 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
 }
 
 
-private class Accounts.ServiceProviderRow :
-    AccountRow<EditorServersPane,Gtk.Label> {
-
-
-    public ServiceProviderRow(Geary.AccountInformation account) {
-        base(
-            account,
-            // Translators: This label describes service hosting the
-            // email account, e.g. GMail, Yahoo, Outlook.com, or some
-            // other generic IMAP service.
-            _("Service provider"),
-            new Gtk.Label("")
-        );
-
-        // Can't change this, so dim it out
-        this.value.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
-        set_activatable(false);
-
-        update();
-    }
-
-    public override void update() {
-        string? details = this.account.service_label;
-        switch (this.account.service_provider) {
-        case Geary.ServiceProvider.GMAIL:
-            details = _("GMail");
-            break;
-
-        case Geary.ServiceProvider.OUTLOOK:
-            details = _("Outlook.com");
-            break;
-
-        case Geary.ServiceProvider.YAHOO:
-            details = _("Yahoo");
-            break;
-        }
-        this.value.set_text(details);
-    }
-
-}
-
-
 private class Accounts.AccountProviderRow :
     AccountRow<EditorServersPane,Gtk.Label> {
 
diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala
index a3f8149e..6543486a 100644
--- a/src/client/application/geary-application.vala
+++ b/src/client/application/geary-application.vala
@@ -264,6 +264,16 @@ public class GearyApplication : Gtk.Application {
         is_destroyed = true;
     }
 
+    public void show_accounts() {
+        activate();
+
+        Accounts.Editor editor = new Accounts.Editor(this, get_active_window());
+        editor.run();
+        editor.destroy();
+        this.controller.expunge_accounts.begin();
+    }
+
+
     public File get_user_data_directory() {
         return File.new_for_path(Environment.get_user_data_dir()).get_child("geary");
     }
@@ -422,10 +432,7 @@ public class GearyApplication : Gtk.Application {
     }
 
     private void on_activate_accounts() {
-        Accounts.Editor editor = new Accounts.Editor(this, get_active_window());
-        editor.run();
-        editor.destroy();
-        this.controller.expunge_accounts.begin();
+        show_accounts();
     }
 
     private void on_activate_compose() {
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index c2a2a072..7ff11309 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -129,7 +129,6 @@ public class GearyController : Geary.BaseObject {
     private UpgradeDialog upgrade_dialog;
     private Gee.List<string> pending_mailtos = new Gee.ArrayList<string>();
     private Geary.Nonblocking.Mutex untrusted_host_prompt_mutex = new Geary.Nonblocking.Mutex();
-    private Gee.HashSet<Geary.Endpoint> validating_endpoints = new Gee.HashSet<Geary.Endpoint>();
 
     private uint operation_count = 0;
     private Geary.Revokable? revokable = null;
@@ -301,7 +300,7 @@ public class GearyController : Geary.BaseObject {
             error("Error migrating configuration directories: %s", e.message);
         }
 
-        // Start Geary.
+        // Hook up accounts and credentials machinery
         this.account_manager = new Accounts.Manager(
             this.application,
             this.application.get_user_config_directory(),
@@ -329,13 +328,19 @@ public class GearyController : Geary.BaseObject {
             warning("Error opening GOA: %s", err.message);
         }
 
+        // Start the engine and load our accounts
         try {
             yield engine.open_async(
                 this.application.get_resource_directory(), cancellable
             );
             yield this.account_manager.load_accounts(cancellable);
             if (engine.get_accounts().size == 0) {
-                create_account();
+                this.application.show_accounts();
+                if (engine.get_accounts().size == 0) {
+                    // User cancelled without creating an account, so
+                    // nothing else to do but exit.
+                    this.application.quit();
+                }
             }
         } catch (Error e) {
             warning("Error opening Geary.Engine instance: %s", e.message);
@@ -491,7 +496,6 @@ public class GearyController : Geary.BaseObject {
         this.current_folder = null;
 
         this.previous_non_search_folder = null;
-        this.validating_endpoints.clear();
 
         this.selected_conversations = new Gee.HashSet<Geary.App.Conversation>();
         this.last_deleted_conversation = null;
@@ -700,10 +704,7 @@ public class GearyController : Geary.BaseObject {
                 peer, err.message);
         }
 
-        // if these are in validation, there are complex GTK and workflow issues from simply
-        // presenting the prompt now, so caller who connected will need to do it on their own dime
-        if (!this.validating_endpoints.contains(service.endpoint))
-            prompt_for_untrusted_host(main_window, account, service, false);
+        prompt_for_untrusted_host(main_window, account, service, false);
     }
 
     private void prompt_for_untrusted_host(Gtk.Window? parent,
@@ -747,23 +748,6 @@ public class GearyController : Geary.BaseObject {
             break;
         }
     }
-    
-    private void create_account() {
-        Geary.AccountInformation? account_information = request_account_information(null);
-        if (account_information != null)
-            do_validate_until_successful_async.begin(account_information);
-    }
-    
-    private async void do_validate_until_successful_async(Geary.AccountInformation account_information,
-        Cancellable? cancellable = null) {
-        Geary.AccountInformation? result = account_information;
-        do {
-            result = yield validate_or_retry_async(result, cancellable);
-        } while (result != null);
-        
-        if (login_dialog != null)
-            login_dialog.hide();
-    }
 
     // Returns possibly modified validation results
     private Geary.Engine.ValidationResult validation_check_endpoint_for_tls_warnings(
@@ -850,96 +834,7 @@ public class GearyController : Geary.BaseObject {
         
         return validation_result;
     }
-    
-    // Returns null if we are done validating, or the revised account information if we should retry.
-    private async Geary.AccountInformation? validate_or_retry_async(Geary.AccountInformation 
account_information,
-        Cancellable? cancellable = null) {
-        Geary.Engine.ValidationResult result = yield validate_async(account_information,
-            Geary.Engine.ValidationOption.CHECK_CONNECTIONS, cancellable);
-        if (result == Geary.Engine.ValidationResult.OK)
-            return null;
-        
-        // check Endpoints for trust (TLS) issues
-        bool retry_required;
-        result = yield validation_check_for_tls_warnings_async(account_information, result,
-            out retry_required);
-        
-        // return for retry if required; check can also change validation results, in which case
-        // revalidate entirely to have them written out
-        if (retry_required)
-            return account_information;
-        
-        debug("Validation failed. Prompting user for revised account information");
-        Geary.AccountInformation? new_account_information =
-            request_account_information(account_information, result);
-        
-        // If the user refused to enter account information. There is currently no way that we
-        // could see this--we exit in request_account_information, and the only way that an
-        // exit could be canceled is if there are unsaved composer windows open (which won't
-        // happen before an account is created). However, best to include this check for the
-        // future.
-        if (new_account_information == null)
-            return null;
-        
-        debug("User entered revised account information, retrying validation");
-        return new_account_information;
-    }
-    
-    // Attempts to validate and add an account.  Returns a result code indicating
-    // success or one or more errors.
-    public async Geary.Engine.ValidationResult validate_async(
-        Geary.AccountInformation account_information, Geary.Engine.ValidationOption options,
-        Cancellable? cancellable = null) {
-        // add Endpoints to set of validating endpoints to prevent the prompt from appearing
-        validating_endpoints.add(account_information.imap.endpoint);
-        validating_endpoints.add(account_information.smtp.endpoint);
-
-        Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK;
-        // try {
-        //     result = yield Geary.Engine.instance.validate_account_information_async(account_information,
-        //         options, cancellable);
-        // } catch (Error err) {
-        //     debug("Error validating account: %s", err.message);
-        //     this.application.exit(-1); // Fatal error
-
-        //     return result;
-        // }
-
-        validating_endpoints.remove(account_information.imap.endpoint);
-        validating_endpoints.remove(account_information.smtp.endpoint);
-
-        if (result == Geary.Engine.ValidationResult.OK) {
-            Geary.AccountInformation real_account_information = account_information;
-            if (account_information.is_copy) {
-                // We have a temporary copy of the account.  Find the "real" acct info object and
-                // copy the new data into it.
-                real_account_information = get_real_account_information(account_information);
-                real_account_information.copy_from(account_information);
-            }
 
-            try {
-                if (real_account_information.settings_file == null) {
-                    yield this.account_manager.create_account(
-                        real_account_information, cancellable
-                    );
-                } else {
-                    yield this.account_manager.save_account(
-                        real_account_information, cancellable
-                    );
-                }
-                debug("Successfully validated account information");
-            } catch (GLib.Error err) {
-                report_problem(
-                    new Geary.ProblemReport(
-                        Geary.ProblemType.GENERIC_ERROR, err
-                    )
-                );
-            }
-        }
-
-        return result;
-    }
-    
     // Returns the "real" account info associated with a copy.  If it's not a copy, null is returned.
     public Geary.AccountInformation? get_real_account_information(
         Geary.AccountInformation account_information) {
@@ -953,50 +848,6 @@ public class GearyController : Geary.BaseObject {
         
         return null;
     }
-    
-    // Prompt the user for a service, real name, username, and password, and try to start Geary.
-    private Geary.AccountInformation? request_account_information(Geary.AccountInformation? old_info,
-        Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) {
-        Geary.AccountInformation? new_info = old_info;
-        if (login_dialog == null) {
-            // Create here so we know GTK is initialized.
-            login_dialog = new LoginDialog(this.application);
-        } else if (!login_dialog.get_visible()) {
-            // If the dialog has been dismissed, exit here.
-            this.application.exit();
-            return null;
-        }
-        
-        if (new_info != null)
-            login_dialog.set_account_information(new_info, result);
-        
-        login_dialog.present();
-        for (;;) {
-            login_dialog.show_spinner(false);
-            if (login_dialog.run() != Gtk.ResponseType.OK) {
-                debug("User refused to enter account information. Exiting...");
-                this.application.exit(1);
-                return null;
-            }
-            
-            login_dialog.show_spinner(true);
-            new_info = login_dialog.get_account_information();
-            
-            if ((!new_info.imap.use_ssl && !new_info.imap.use_starttls)
-                || (!new_info.smtp.use_ssl && !new_info.smtp.use_starttls)) {
-                ConfirmationDialog security_dialog = new ConfirmationDialog(main_window,
-                    _("Your settings are insecure"),
-                    _("Your IMAP and/or SMTP settings do not specify SSL or TLS.  This means your username 
and password could be read by another person on the network.  Are you sure you want to do this?"),
-                    _("Co_ntinue"));
-                if (security_dialog.run() != Gtk.ResponseType.OK)
-                    continue;
-            }
-            
-            break;
-        }
-        
-        return new_info;
-    }
 
     private void report_problem(Geary.ProblemReport report) {
         debug("Problem reported: %s", report.to_string());
diff --git a/src/client/components/components-validator.vala b/src/client/components/components-validator.vala
index be23e122..9da21c75 100644
--- a/src/client/components/components-validator.vala
+++ b/src/client/components/components-validator.vala
@@ -308,3 +308,97 @@ public class Components.EmailValidator : Validator {
     }
 
 }
+
+
+/**
+ * A validator for GTK Entry widgets that contain a network address.
+ *
+ * This attempts parse the entry value as a host name or IP address
+ * with an optional port, then resolve the host name if
+ * needed. Parsing is performed by {@link GLib.NetworkAddress.parse}
+ * to parse the user input, hence it may be specified in any form
+ * supported by that method.
+ */
+public class Components.NetworkAddressValidator : Validator {
+
+
+    /** The validated network address, if any. */
+    public GLib.NetworkAddress? validated_address {
+        get; private set; default = null;
+    }
+
+    /** The default port used when parsing the address. */
+    public uint16 default_port { get; private set; }
+
+    private GLib.Resolver resolver;
+    private GLib.Cancellable? cancellable = null;
+
+
+    public NetworkAddressValidator(Gtk.Entry target, uint16 default_port) {
+        base(target);
+        this.default_port = default_port;
+
+        this.resolver = GLib.Resolver.get_default();
+
+        // Translators: Tooltip used when an entry requires a valid,
+        // resolvable server name to be entered, but one is not
+        // provided.
+        this.empty_state.icon_tooltip_text = _("A server name is required");
+
+        // Translators: Tooltip used when an entry requires a valid
+        // server name to be entered, but it was unable to be
+        // looked-up in the DNS.
+        this.invalid_state.icon_tooltip_text = _("Could not look up server name");
+    }
+
+
+    public override Validator.Validity validate(string value,
+                                                Validator.Trigger reason) {
+        if (this.cancellable != null) {
+            this.cancellable.cancel();
+        }
+
+        GLib.NetworkAddress? address = null;
+        try {
+            address = GLib.NetworkAddress.parse(value, this.default_port);
+        } catch (GLib.Error err) {
+            debug("Error parsing host name \"%s\": %s", value, err.message);
+        }
+
+        Validator.Validity ret = this.state;
+
+        if (address != null) {
+            // Only re-validate if changed
+            if (this.validated_address == null ||
+                this.validated_address.hostname != address.hostname ||
+                this.validated_address.port != address.port ||
+                this.validated_address.scheme != address.scheme) {
+
+                this.cancellable = new GLib.Cancellable();
+                this.resolver.lookup_by_name_async.begin(
+                    value.strip(), this.cancellable,
+                    (obj, res) => {
+                        try {
+                            this.resolver.lookup_by_name_async.end(res);
+                            this.validated_address = address;
+                            update_state(Validator.Validity.VALID);
+                        } catch (GLib.IOError.CANCELLED err) {
+                            this.validated_address = null;
+                        } catch (GLib.Error err) {
+                            this.validated_address = null;
+                            update_state(Validator.Validity.INVALID);
+                        }
+                        this.cancellable = null;
+                    }
+                );
+
+                ret = Validator.Validity.IN_PROGRESS;
+            }
+        } else {
+            ret = Validator.Validity.INVALID;
+        }
+
+        return ret;
+    }
+
+}
diff --git a/src/client/meson.build b/src/client/meson.build
index baede7ac..5fae0786 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -20,6 +20,7 @@ geary_client_vala_sources = files(
   'accounts/account-dialog-spinner-pane.vala',
   'accounts/account-spinner-page.vala',
   'accounts/accounts-editor.vala',
+  'accounts/accounts-editor-add-pane.vala',
   'accounts/accounts-editor-edit-pane.vala',
   'accounts/accounts-editor-list-pane.vala',
   'accounts/accounts-editor-remove-pane.vala',
diff --git a/src/engine/api/geary-service-information.vala b/src/engine/api/geary-service-information.vala
index 0e98d431..fbdbe8bf 100644
--- a/src/engine/api/geary-service-information.vala
+++ b/src/engine/api/geary-service-information.vala
@@ -34,6 +34,71 @@ public enum Geary.Protocol {
 }
 
 
+/** The method used to negotiate a TLS session, if any. */
+public enum Geary.TlsNegotiationMethod {
+    /** No TLS session should be established. */
+    NONE,
+    /** StartTLS should used to establish a session. */
+    START_TLS,
+    /** A TLS session should be established at the transport layer. */
+    TRANSPORT;
+
+
+    public static TlsNegotiationMethod for_value(string value)
+        throws EngineError {
+        switch (value.ascii_up()) {
+        case "NONE":
+            return NONE;
+        case "START_TLS":
+            return START_TLS;
+        case "TRANSPORT":
+            return TRANSPORT;
+        }
+        throw new EngineError.BAD_PARAMETERS(
+            "Unknown Protocol value: %s", value
+        );
+    }
+
+    public string to_value() {
+        string value = to_string();
+        return value.substring(value.last_index_of("_") + 1);
+    }
+
+}
+
+
+/** The credentials used to negotiate SMTP authentication, if any. */
+public enum Geary.SmtpCredentials {
+    /** No SMTP credentials are required. */
+    NONE,
+    /** The account's IMAP credentials should be used. */
+    IMAP,
+    /** Custom credentials are required for SMTP. */
+    CUSTOM;
+
+    public static SmtpCredentials for_value(string value)
+        throws EngineError {
+        switch (value.ascii_up()) {
+        case "NONE":
+            return Geary.SmtpCredentials.NONE;
+        case "IMAP":
+            return Geary.SmtpCredentials.IMAP;
+        case "CUSTOM":
+            return Geary.SmtpCredentials.CUSTOM;
+        }
+        throw new EngineError.BAD_PARAMETERS(
+            "Unknown SmtpCredentials value: %s", value
+        );
+    }
+
+    public string to_value() {
+        string value = to_string();
+        return value.substring(value.last_index_of("_") + 1);
+    }
+
+}
+
+
 /**
  * This class encloses all the information used when connecting with the server,
  * how to authenticate with it and which credentials to use. Derived classes
diff --git a/ui/accounts_editor_add_pane.ui b/ui/accounts_editor_add_pane.ui
new file mode 100644
index 00000000..fb381c9c
--- /dev/null
+++ b/ui/accounts_editor_add_pane.ui
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="AccountsEditorAddPane" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkOverlay" id="osd_overlay">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkViewport">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="details_list">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="selection_mode">none</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkGrid" id="receiving_panel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Receiving</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
+                            <style>
+                              <class name="geary-settings-heading"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFrame">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkListBox" id="receiving_list">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="selection_mode">none</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkGrid" id="sending_panel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Sending</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
+                            <style>
+                              <class name="geary-settings-heading"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFrame">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkListBox" id="sending_list">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="selection_mode">none</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <style>
+                      <class name="geary-account-view"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="index">-1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+  </template>
+  <object class="GtkHeaderBar" id="header">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="title">Add an account</property>
+    <property name="has_subtitle">False</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="no_show_all">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton" id="create_button">
+            <property name="label" translatable="yes">Create</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_create_button_clicked" swapped="no"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="pack_type">end</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/ui/accounts_editor_list_pane.ui b/ui/accounts_editor_list_pane.ui
index db6c70d3..96e9bbec 100644
--- a/ui/accounts_editor_list_pane.ui
+++ b/ui/accounts_editor_list_pane.ui
@@ -9,6 +9,8 @@
       <object class="GtkOverlay" id="osd_overlay">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
         <child>
           <object class="GtkScrolledWindow">
             <property name="visible">True</property>
@@ -26,7 +28,64 @@
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <child>
-                      <object class="GtkFrame">
+                      <object class="GtkGrid" id="welcome_panel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="pixel_size">64</property>
+                            <property name="icon_name">org.gnome.Geary</property>
+                            <property name="use_fallback">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">0</property>
+                            <property name="top_attach">0</property>
+                            <property name="height">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="valign">start</property>
+                            <property name="label" translatable="yes">To get started, select a email 
provider below.</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="valign">end</property>
+                            <property name="label" translatable="yes">Welcome to Geary</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="top_attach">0</property>
+                          </packing>
+                        </child>
+                        <style>
+                          <class name="geary-welcome-panel"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFrame" id="accounts_list_frame">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="hexpand">True</property>
@@ -39,7 +98,7 @@
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
                             <property name="selection_mode">none</property>
-                            <signal name="row-activated" handler="on_accounts_list_row_activated" 
swapped="no"/>
+                            <signal name="row-activated" handler="on_row_activated" swapped="no"/>
                           </object>
                         </child>
                         <child type="label_item">
@@ -48,7 +107,53 @@
                       </object>
                       <packing>
                         <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="add_service_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Add an account</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                        <style>
+                          <class name="geary-settings-heading"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="valign">start</property>
+                        <property name="hexpand">True</property>
+                        <property name="vexpand">True</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="service_list">
+                            <property name="width_request">0</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">start</property>
+                            <property name="selection_mode">none</property>
+                            <signal name="row-activated" handler="on_row_activated" swapped="no"/>
+                          </object>
+                        </child>
+                        <child type="label_item">
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
                       </packing>
                     </child>
                     <style>
diff --git a/ui/geary.css b/ui/geary.css
index 2055b4e4..d01150d3 100644
--- a/ui/geary.css
+++ b/ui/geary.css
@@ -194,6 +194,10 @@ grid.geary-account-view image:dir(rtl) {
   margin-left: 6px;
 }
 
+grid.geary-welcome-panel {
+  margin-bottom: 32px;
+}
+
 label.geary-settings-heading {
   font-weight: bold;
   margin-top: 24px;
@@ -204,11 +208,6 @@ row.geary-settings {
   padding: 0px;
 }
 
-row.geary-settings > grid > * {
-  margin: 0px;
-  padding: 0px;
-}
-
 row.geary-settings > grid > * {
   margin: 18px 0;
 }
@@ -236,6 +235,7 @@ frame.geary-settings.geary-signature {
 
 
 row.geary-settings > grid > combobox,
+row.geary-settings > grid > entry,
 row.geary-settings:not(.geary-add-row) > grid > image,
 row.geary-settings > grid > switch {
   /* These use more space than labels, so set their valign to center
@@ -249,9 +249,9 @@ buttonbox.geary-settings  {
 }
 
 popover.geary-editor > grid {
-  margin: 6px;
+  margin: 12px;
 }
 
 popover.geary-editor > grid > button.geary-setting-remove {
-  margin-top: 6px;
+  margin-top: 12px;
 }
diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml
index ba31d74f..ee943676 100644
--- a/ui/org.gnome.Geary.gresource.xml
+++ b/ui/org.gnome.Geary.gresource.xml
@@ -4,6 +4,7 @@
     <file compressed="true" preprocess="xml-stripblanks">account_cannot_remove.glade</file>
     <file compressed="true" preprocess="xml-stripblanks">account_list.glade</file>
     <file compressed="true" preprocess="xml-stripblanks">account_spinner.glade</file>
+    <file compressed="true" preprocess="xml-stripblanks">accounts_editor_add_pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">accounts_editor_edit_pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">accounts_editor_list_pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">accounts_editor_remove_pane.ui</file>


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