[geary/gnumdk/autoconfig: 14/15] client: accounts: Add support for Thunderbird autoconfig




commit a1a42505d703f56b922dc7917e77ace360fbbd1b
Author: Cédric Bellegarde <cedric bellegarde adishatz org>
Date:   Thu Aug 25 23:12:45 2022 +0200

    client: accounts: Add support for Thunderbird autoconfig
    
    - Auto detect server settings
    - Rework accounts editor add panel
    
    Fix #1390
    Fix #1350

 meson.build                                        |   1 +
 src/client/accounts/accounts-autoconfig.vala       | 170 +++++++++++
 src/client/accounts/accounts-editor-add-pane.vala  | 336 ++++++++++++++-------
 src/client/accounts/accounts-editor-list-pane.vala | 103 +------
 src/client/meson.build                             |   1 +
 ui/accounts_editor_add_pane.ui                     | 179 ++++++-----
 ui/accounts_editor_list_pane.ui                    |  58 +---
 7 files changed, 496 insertions(+), 352 deletions(-)
---
diff --git a/meson.build b/meson.build
index 1be205e31..df2ee5a86 100644
--- a/meson.build
+++ b/meson.build
@@ -93,6 +93,7 @@ libhandy = dependency('libhandy-1', version: '>= 1.2.1', required: false)
 libmath = cc.find_library('m')
 libpeas = dependency('libpeas-1.0', version: '>= 1.24.0')
 libsecret = dependency('libsecret-1', version: '>= 0.11')
+libsoup = dependency('libsoup-3.0')
 libstemmer_dep = cc.find_library('stemmer')
 libunwind_dep = dependency(
   'libunwind', version: '>= 1.1', required: get_option('libunwind')
diff --git a/src/client/accounts/accounts-autoconfig.vala b/src/client/accounts/accounts-autoconfig.vala
new file mode 100644
index 000000000..89684d7b7
--- /dev/null
+++ b/src/client/accounts/accounts-autoconfig.vala
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2022 Cédric Bellegarde <cedric bellegarde adishatz org>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * Thunderbird autoconfig XML values
+ */
+internal class Accounts.AutoConfigValues {
+    // emailProvider.id
+    public string id { get; set; default = ""; }
+
+    // incomingServer[type="imap"].hostname
+    public string imap_server { get; set; default = ""; }
+    // incomingServer[type="imap"].port
+    public string imap_port { get; set; default = ""; }
+    // incomingServer[type="imap"].socketType
+    public Geary.TlsNegotiationMethod imap_tls_method {
+        get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
+    }
+
+    // outgoingServer[type="smtp"].hostname
+    public string smtp_server{ get; set; default = ""; }
+    // outgoingServer[type="smtp"].port
+    public string smtp_port { get; set; default = ""; }
+    // outgoingServer[type="smtp"].socketType
+    public Geary.TlsNegotiationMethod smtp_tls_method {
+        get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
+    }
+
+}
+
+internal errordomain Accounts.AutoConfigError {
+    ERROR
+}
+
+/**
+ * An account autoconfiguration helper
+ */
+internal class Accounts.AutoConfig {
+
+    private static string AUTOCONFIG_BASE_URI = "https://autoconfig.thunderbird.net/v1.1/";;
+    private static string AUTOCONFIG_PATH = "/mail/config-v1.1.xml";
+
+    private unowned GLib.Cancellable cancellable;
+
+    internal AutoConfig(GLib.Cancellable auto_config_cancellable) {
+        cancellable = auto_config_cancellable;
+    }
+
+    public async AutoConfigValues get_config(string hostname)
+            throws AutoConfigError {
+        AutoConfigValues auto_config_values;
+
+        // First try to get config from mail domain, then from thunderbird
+        try {
+            auto_config_values = yield get_config_for_uri(
+                "http://autoconfig."; + hostname + AUTOCONFIG_PATH
+            );
+        } catch (AutoConfigError err) {
+            auto_config_values = yield get_config_for_uri(
+                AUTOCONFIG_BASE_URI + hostname
+            );
+        }
+        return auto_config_values;
+    }
+
+    private async AutoConfigValues get_config_for_uri(string uri)
+            throws AutoConfigError {
+        GLib.InputStream stream;
+        var session = new Soup.Session();
+        var msg = new Soup.Message("GET", uri);
+
+        try {
+            stream = yield session.send_async(
+                msg, Priority.DEFAULT, this.cancellable
+            );
+        } catch (GLib.Error err) {
+            throw new AutoConfigError.ERROR(err.message);
+        }
+
+        try {
+            var stdout_stream = new MemoryOutputStream.resizable();
+            yield stdout_stream.splice_async(
+                stream, 0, Priority.DEFAULT, null
+            );
+            stdout_stream.write("\0".data);
+            stdout_stream.close();
+            unowned var xml_data = (string) stdout_stream.get_data();
+            return get_config_for_xml(xml_data);
+        } catch (GLib.Error err) {
+            throw new AutoConfigError.ERROR(err.message);
+        } finally {
+            try {
+                yield stream.close_async();
+            } catch (GLib.Error err) {
+                // Oh well
+            }
+        }
+    }
+
+    private AutoConfigValues get_config_for_xml(string xml_data)
+            throws AutoConfigError {
+        unowned Xml.Doc doc = Xml.Parser.parse_memory(xml_data, xml_data.length);
+        if (doc == null) {
+            throw new AutoConfigError.ERROR("Invalid XML");
+        }
+
+        unowned Xml.Node root = doc.get_root_element();
+        unowned Xml.Node email_provider = get_node(root, "emailProvider");
+        unowned Xml.Node incoming_server = get_node(email_provider, "incomingServer");
+        unowned Xml.Node outgoing_server = get_node(email_provider, "outgoingServer");
+
+        if (incoming_server == null || outgoing_server == null) {
+            throw new AutoConfigError.ERROR("Invalid XML");
+        }
+
+        if (incoming_server.get_prop("type") != "imap" ||
+                outgoing_server.get_prop("type") != "smtp") {
+            throw new AutoConfigError.ERROR("Unsupported protocol");
+        }
+
+        var auto_config_values = new AutoConfigValues();
+
+        auto_config_values.id = email_provider.get_prop("id");
+
+        auto_config_values.imap_server = get_node_value(incoming_server, "hostname");
+        auto_config_values.imap_port = get_node_value(incoming_server, "port");
+        auto_config_values.imap_tls_method = get_tls_method(
+            get_node_value(incoming_server, "socketType")
+        );
+
+        auto_config_values.smtp_server = get_node_value(outgoing_server, "hostname");
+        auto_config_values.smtp_port = get_node_value(outgoing_server, "port");
+        auto_config_values.smtp_tls_method = get_tls_method(
+            get_node_value(outgoing_server, "socketType")
+        );
+
+        return auto_config_values;
+    }
+
+    private unowned Xml.Node? get_node(Xml.Node root, string name) {
+        for (unowned Xml.Node entry = root.children; entry != null; entry = entry.next) {
+            if (entry.type == Xml.ElementType.ELEMENT_NODE && entry.name == name) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
+    private string get_node_value(Xml.Node root, string name) {
+        unowned Xml.Node? node = get_node(root, name);
+        if (node == null)
+          return "";
+        return node.get_content();
+    }
+
+    private Geary.TlsNegotiationMethod get_tls_method(string method) {
+        switch (method) {
+        case "SSL":
+            return Geary.TlsNegotiationMethod.TRANSPORT;
+        case "STARTTLS":
+            return Geary.TlsNegotiationMethod.START_TLS;
+        default:
+            return Geary.TlsNegotiationMethod.NONE;
+        }
+    }
+}
diff --git a/src/client/accounts/accounts-editor-add-pane.vala 
b/src/client/accounts/accounts-editor-add-pane.vala
index 647711f47..1eb4eda3a 100644
--- a/src/client/accounts/accounts-editor-add-pane.vala
+++ b/src/client/accounts/accounts-editor-add-pane.vala
@@ -37,31 +37,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
 
     [GtkChild] private unowned Gtk.HeaderBar header;
 
-    [GtkChild] private unowned Gtk.Grid pane_content;
+    [GtkChild] private unowned Gtk.Stack stack;
 
     [GtkChild] private unowned Gtk.Adjustment pane_adjustment;
 
     [GtkChild] private unowned Gtk.ListBox details_list;
 
-    [GtkChild] private unowned Gtk.Grid receiving_panel;
-
     [GtkChild] private unowned Gtk.ListBox receiving_list;
 
-    [GtkChild] private unowned Gtk.Grid sending_panel;
-
     [GtkChild] private unowned Gtk.ListBox sending_list;
 
-    [GtkChild] private unowned Gtk.Button create_button;
+    [GtkChild] private unowned Gtk.Button action_button;
 
     [GtkChild] private unowned Gtk.Button back_button;
 
-    [GtkChild] private unowned Gtk.Spinner create_spinner;
+    [GtkChild] private unowned Gtk.Spinner action_spinner;
 
     private NameRow real_name;
     private EmailRow email = new EmailRow();
     private string last_valid_email = "";
     private string last_valid_hostname = "";
 
+    private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
+
     private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
     private TransportSecurityRow imap_tls = new TransportSecurityRow();
     private LoginRow imap_login = new LoginRow();
@@ -76,32 +74,19 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     private bool controls_valid = false;
 
 
-    internal EditorAddPane(Editor editor, Geary.ServiceProvider provider) {
+    internal EditorAddPane(Editor editor) {
         this.editor = editor;
-        this.provider = provider;
+        this.provider = Geary.ServiceProvider.OTHER;
 
         this.accounts = editor.application.controller.account_manager;
         this.engine = editor.application.engine;
 
-        this.pane_content.set_focus_vadjustment(this.pane_adjustment);
+        this.stack.set_focus_vadjustment(this.pane_adjustment);
 
         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.
-                    _("All others")
-                )
-            );
-            this.receiving_panel.hide();
-            this.sending_panel.hide();
-        }
-
         this.real_name = new NameRow(this.accounts.get_account_name());
 
         this.details_list.add(this.real_name);
@@ -130,18 +115,14 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         this.smtp_password.validator.state_changed.connect(on_validated);
         this.smtp_password.value.activate.connect(on_activated);
 
-        if (provider == Geary.ServiceProvider.OTHER) {
-            this.receiving_list.add(this.imap_hostname);
-            this.receiving_list.add(this.imap_tls);
-            this.receiving_list.add(this.imap_login);
-            this.receiving_list.add(this.imap_password);
+        this.receiving_list.add(this.imap_hostname);
+        this.receiving_list.add(this.imap_tls);
+        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_tls);
-            this.sending_list.add(this.smtp_auth);
-        } else {
-            this.details_list.add(this.imap_password);
-        }
+        this.sending_list.add(this.smtp_hostname);
+        this.sending_list.add(this.smtp_tls);
+        this.sending_list.add(this.smtp_auth);
     }
 
     internal Gtk.HeaderBar get_header() {
@@ -169,7 +150,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         account.outgoing = new_smtp_service();
         account.untrusted_host.connect(on_untrusted_host);
 
-        if (this.provider == Geary.ServiceProvider.OTHER) {
+        if (this.provider == Geary.ServiceProvider.OTHER &&
+                this.imap_hostname.get_visible()) {
             bool imap_valid = false;
             bool smtp_valid = false;
 
@@ -302,30 +284,22 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             Geary.Protocol.IMAP, this.provider
         );
 
-        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()
-            );
+        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;
-            service.transport_security = this.imap_tls.value.method;
+        Components.NetworkAddressValidator host =
+            (Components.NetworkAddressValidator)
+            this.imap_hostname.validator;
+        GLib.NetworkAddress address = host.validated_address;
+        service.host = address.hostname;
+        service.port = (uint16) address.port;
+        service.transport_security = this.imap_tls.value.method;
 
-            if (service.port == 0) {
-                service.port = service.get_default_port();
-            }
-        } else {
-            service.credentials = new Geary.Credentials(
-                Geary.Credentials.Method.PASSWORD,
-                this.email.value.get_text().strip(),
-                this.imap_password.value.get_text().strip()
-            );
+        if (service.port == 0) {
+            service.port = service.get_default_port();
         }
 
         return service;
@@ -336,90 +310,204 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             Geary.Protocol.SMTP, this.provider
         );
 
-        if (this.provider == Geary.ServiceProvider.OTHER) {
-            service.credentials_requirement = this.smtp_auth.value.source;
-            if (service.credentials_requirement ==
-                    Geary.Credentials.Requirement.CUSTOM) {
-                service.credentials = new Geary.Credentials(
-                    Geary.Credentials.Method.PASSWORD,
-                    this.smtp_login.value.get_text().strip(),
-                    this.smtp_password.value.get_text().strip()
-                );
-            }
+        service.credentials_requirement = this.smtp_auth.value.source;
+        if (service.credentials_requirement ==
+                Geary.Credentials.Requirement.CUSTOM) {
+            service.credentials = new Geary.Credentials(
+                Geary.Credentials.Method.PASSWORD,
+                this.smtp_login.value.get_text().strip(),
+                this.smtp_password.value.get_text().strip()
+            );
+        }
 
-            Components.NetworkAddressValidator host =
-                (Components.NetworkAddressValidator)
-                this.smtp_hostname.validator;
-            GLib.NetworkAddress address = host.validated_address;
+        Components.NetworkAddressValidator host =
+            (Components.NetworkAddressValidator)
+            this.smtp_hostname.validator;
+        GLib.NetworkAddress address = host.validated_address;
 
-            service.host = address.hostname;
-            service.port = (uint16) address.port;
-            service.transport_security = this.smtp_tls.value.method;
+        service.host = address.hostname;
+        service.port = (uint16) address.port;
+        service.transport_security = this.smtp_tls.value.method;
 
-            if (service.port == 0) {
-                service.port = service.get_default_port();
-            }
+        if (service.port == 0) {
+            service.port = service.get_default_port();
         }
 
         return service;
     }
 
     private void check_validation() {
+        bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
         bool controls_valid = true;
-        foreach (Gtk.ListBox list in new Gtk.ListBox[] {
+        Gtk.ListBox[] list_boxes;
+        if (server_settings_visible) {
+            list_boxes = new Gtk.ListBox[] {
                 this.details_list, this.receiving_list, this.sending_list
-            }) {
-            list.foreach((child) => {
+            };
+        } else {
+            list_boxes = new Gtk.ListBox[] { this.details_list };
+        }
+        foreach (Gtk.ListBox list_box in list_boxes) {
+            list_box.foreach((child) => {
                     AddPaneRow? validatable = child as AddPaneRow;
                     if (validatable != null && !validatable.validator.is_valid) {
                         controls_valid = false;
                     }
                 });
         }
-        this.create_button.set_sensitive(controls_valid);
+        this.action_button.set_sensitive(controls_valid);
         this.controls_valid = controls_valid;
     }
 
     private void update_operation_ui(bool is_running) {
-        this.create_spinner.visible = is_running;
-        this.create_spinner.active = is_running;
-        this.create_button.sensitive = !is_running;
+        this.action_spinner.visible = is_running;
+        this.action_spinner.active = is_running;
+        this.action_button.sensitive = !is_running;
         this.back_button.sensitive = !is_running;
         this.sensitive = !is_running;
     }
 
+    private void switch_to_user_settings() {
+        this.stack.set_visible_child_name("user_settings");
+        this.action_button.set_label(_("_Next"));
+        this.action_button.set_sensitive(true);
+        this.action_button.get_style_context().remove_class("suggested-action");
+    }
+
+    private void switch_to_server_settings() {
+        this.stack.set_visible_child_name("server_settings");
+        this.action_button.set_label(_("_Create"));
+        this.action_button.set_sensitive(false);
+        this.action_button.get_style_context().add_class("suggested-action");
+    }
+
+    private void set_server_settings_from_autoconfig(AutoConfig auto_config,
+                                                     GLib.AsyncResult res)
+            throws Accounts.AutoConfigError {
+        AutoConfigValues auto_config_values = auto_config.get_config.end(res);
+        Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
+        Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
+        TlsComboBox imap_tls_combo_box = this.imap_tls.value;
+        TlsComboBox smtp_tls_combo_box = this.smtp_tls.value;
+
+        imap_hostname_entry.text = auto_config_values.imap_server +
+             ":" + auto_config_values.imap_port;
+        smtp_hostname_entry.text = auto_config_values.smtp_server +
+             ":" + auto_config_values.smtp_port;
+        imap_tls_combo_box.method = auto_config_values.imap_tls_method;
+        smtp_tls_combo_box.method = auto_config_values.smtp_tls_method;
+
+        this.imap_hostname.hide();
+        this.smtp_hostname.hide();
+        this.imap_tls.hide();
+        this.smtp_tls.hide();
+
+        switch (auto_config_values.id) {
+        case "googlemail.com":
+            this.provider = Geary.ServiceProvider.GMAIL;
+            break;
+        case "hotmail.com":
+            this.provider = Geary.ServiceProvider.OUTLOOK;
+            break;
+        default:
+            this.provider = Geary.ServiceProvider.OTHER;
+            break;
+        }
+    }
+
+    private void set_server_settings_from_hostname(string hostname) {
+        Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
+        Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
+        string smtp_hostname = "smtp." + hostname;
+        string imap_hostname = "imap." + hostname;
+        string last_imap_hostname = "";
+        string last_smtp_hostname = "";
+
+        this.imap_hostname.show();
+        this.smtp_hostname.show();
+
+        if (this.last_valid_hostname != "") {
+            last_imap_hostname = "imap." + this.last_valid_hostname;
+            last_smtp_hostname = "smtp." + this.last_valid_hostname;
+        }
+        if (imap_hostname_entry.text == last_imap_hostname) {
+            imap_hostname_entry.text = imap_hostname;
+        }
+        if (smtp_hostname_entry.text == last_smtp_hostname) {
+            smtp_hostname_entry.text = smtp_hostname;
+        }
+        this.last_valid_hostname = hostname;
+    }
+
+    private void add_goa_account() {
+        this.accounts.add_goa_account.begin(
+            this.provider, this.op_cancellable,
+            (obj, res) => {
+                bool add_local = false;
+                try {
+                    this.accounts.add_goa_account.end(res);
+                } catch (GLib.IOError.NOT_SUPPORTED err) {
+                    // Not a supported type, so don't bother logging the error
+                    add_local = true;
+                } catch (GLib.Error err) {
+                    debug("Failed to add %s via GOA: %s",
+                          this.provider.to_string(), err.message);
+                    add_local = true;
+                }
+                // Google Mail does not support "Less secure apps" anymore
+                if (add_local) {
+                    switch (this.provider) {
+                    case Geary.ServiceProvider.GMAIL:
+                        this.editor.add_notification(
+                            new Components.InAppNotification(
+                                // Translators: In-app notification label, when
+                                // GNOME Online Accounts are missing
+                                _("Online accounts are missing")
+                            )
+                        );
+                        add_local = false;
+                        break;
+                    default:
+                        break;
+                    }
+                }
+
+                if (add_local) {
+                    switch_to_server_settings();
+                } else {
+                    this.editor.pop();
+                }
+            }
+        );
+    }
+
     private void on_validated(Components.Validator.Trigger reason) {
         check_validation();
         if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
-            this.create_button.clicked();
+            this.action_button.clicked();
         }
     }
 
     private void on_activated() {
         if (this.controls_valid) {
-            this.create_button.clicked();
+            this.action_button.clicked();
         }
     }
 
     private void on_email_changed() {
         Gtk.Entry imap_login_entry = this.imap_login.value;
         Gtk.Entry smtp_login_entry = this.smtp_login.value;
-        Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
-        Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
-        string email = "";
-        string hostname = "";
-        string imap_hostname = "";
-        string smtp_hostname = "";
-        string last_imap_hostname = "";
-        string last_smtp_hostname = "";
 
-        if (this.email.validator.state == Components.Validator.Validity.VALID) {
-            email = this.email.value.text;
-            hostname = email.split("@")[1];
-            smtp_hostname = "smtp." + hostname;
-            imap_hostname = "imap." + hostname;
+        this.auto_config_cancellable.cancel();
+
+        if (this.email.validator.state != Components.Validator.Validity.VALID) {
+            return;
         }
 
+        string email = this.email.value.text;
+        string hostname = email.split("@")[1];
+
+        // Do not update entries if changed by user
         if (imap_login_entry.text == this.last_valid_email) {
             imap_login_entry.text = email;
         }
@@ -427,19 +515,23 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             smtp_login_entry.text = email;
         }
 
-        if (this.last_valid_hostname != "") {
-            last_imap_hostname = "imap." + this.last_valid_hostname;
-            last_smtp_hostname = "smtp." + this.last_valid_hostname;
-        }
-        if (imap_hostname_entry.text == last_imap_hostname) {
-            imap_hostname_entry.text = imap_hostname;
-        }
-        if (smtp_hostname_entry.text == last_smtp_hostname) {
-            smtp_hostname_entry.text = smtp_hostname;
-        }
-
         this.last_valid_email = email;
-        this.last_valid_hostname = hostname;
+
+        // Try to get configuration from Thunderbird autoconfig service
+        this.action_spinner.visible = true;
+        this.action_spinner.active = true;
+        this.auto_config_cancellable = new GLib.Cancellable();
+        var auto_config = new AutoConfig(this.auto_config_cancellable);
+        auto_config.get_config.begin(hostname, (obj, res) => {
+            try {
+                set_server_settings_from_autoconfig(auto_config, res);
+            } catch (Accounts.AutoConfigError err) {
+                debug("Error getting auto configuration: %s", err.message);
+                set_server_settings_from_hostname(hostname);
+            }
+            this.action_spinner.visible = false;
+            this.action_spinner.active = false;
+        });
     }
 
     private void on_smtp_auth_changed() {
@@ -474,13 +566,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     }
 
     [GtkCallback]
-    private void on_create_button_clicked() {
-        this.validate_account.begin(this.op_cancellable);
+    private void on_action_button_clicked() {
+        if (this.stack.get_visible_child_name() == "user_settings") {
+            switch (this.provider) {
+            case Geary.ServiceProvider.GMAIL:
+            case Geary.ServiceProvider.OUTLOOK:
+                add_goa_account();
+                break;
+            case Geary.ServiceProvider.OTHER:
+                switch_to_server_settings();
+                break;
+            }
+        } else {
+            this.validate_account.begin(this.op_cancellable);
+        }
     }
 
     [GtkCallback]
     private void on_back_button_clicked() {
-        this.editor.pop();
+        if (this.stack.get_visible_child_name() == "user_settings") {
+            this.editor.pop();
+        } else {
+            switch_to_user_settings();
+        }
     }
 
     [GtkCallback]
diff --git a/src/client/accounts/accounts-editor-list-pane.vala 
b/src/client/accounts/accounts-editor-list-pane.vala
index c156eeaff..2f0918d83 100644
--- a/src/client/accounts/accounts-editor-list-pane.vala
+++ b/src/client/accounts/accounts-editor-list-pane.vala
@@ -31,7 +31,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
     /** {@inheritDoc} */
     internal Gtk.Widget initial_widget {
         get {
-            return this.show_welcome ? this.service_list : this.accounts_list;
+            return this.accounts_list;
         }
     }
 
@@ -73,10 +73,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
 
     [GtkChild] private unowned Gtk.Frame accounts_list_frame;
 
-    [GtkChild] private unowned Gtk.Label add_service_label;
-
-    [GtkChild] private unowned Gtk.ListBox service_list;
-
     private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
         new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
 
@@ -97,12 +93,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
             add_account(account, this.accounts.get_status(account));
         }
 
-        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);
         this.accounts.account_removed.connect(on_account_removed);
@@ -128,8 +118,8 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
         base.destroy();
     }
 
-    internal void show_new_account(Geary.ServiceProvider provider) {
-        this.editor.push(new EditorAddPane(this.editor, provider));
+    internal void show_new_account() {
+        this.editor.push(new EditorAddPane(this.editor));
     }
 
     internal void show_existing_account(Geary.AccountInformation account) {
@@ -171,13 +161,11 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
             // pane and service list.
             this.welcome_panel.show();
             this.accounts_list_frame.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_frame.show();
-            this.add_service_label.show();
         }
     }
 
@@ -266,21 +254,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
     }
 
     [GtkCallback]
-    private bool on_list_keynav_failed(Gtk.Widget widget,
-                                       Gtk.DirectionType direction) {
-        bool ret = Gdk.EVENT_PROPAGATE;
-        if (direction == Gtk.DirectionType.DOWN &&
-            widget == this.accounts_list) {
-            this.service_list.child_focus(direction);
-            ret = Gdk.EVENT_STOP;
-        } else if (direction == Gtk.DirectionType.UP &&
-            widget == this.service_list) {
-            this.accounts_list.child_focus(direction);
-            ret = Gdk.EVENT_STOP;
-        }
-        return ret;
+    private void on_add_button_clicked() {
+        show_new_account();
     }
-
 }
 
 
@@ -424,75 +400,6 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
 }
 
 
-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 = null;
-        switch (provider) {
-        case Geary.ServiceProvider.GMAIL:
-            name = _("Gmail");
-            break;
-
-        case Geary.ServiceProvider.OUTLOOK:
-            name = _("Outlook.com");
-            break;
-
-        case Geary.ServiceProvider.YAHOO:
-            name = _("Yahoo");
-            break;
-
-        case Geary.ServiceProvider.OTHER:
-            name = _("Other email providers");
-            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.accounts.add_goa_account.begin(
-            this.provider, pane.op_cancellable,
-            (obj, res) => {
-                bool add_local = false;
-                try {
-                    pane.accounts.add_goa_account.end(res);
-                } catch (GLib.IOError.NOT_SUPPORTED err) {
-                    // Not a supported type, so don't bother logging the error
-                    add_local = true;
-                } catch (GLib.Error err) {
-                    debug("Failed to add %s via GOA: %s",
-                          this.provider.to_string(), err.message);
-                    add_local = true;
-                }
-
-                if (add_local) {
-                    pane.show_new_account(this.provider);
-                }
-            });
-    }
-
-}
-
-
 internal class Accounts.ReorderAccountCommand : Application.Command {
 
 
diff --git a/src/client/meson.build b/src/client/meson.build
index 715358320..18ad1ecb1 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -34,6 +34,7 @@ client_vala_sources = files(
   'application/goa-mediator.vala',
   'application/secret-mediator.vala',
 
+  'accounts/accounts-autoconfig.vala',
   'accounts/accounts-editor.vala',
   'accounts/accounts-editor-add-pane.vala',
   'accounts/accounts-editor-edit-pane.vala',
diff --git a/ui/accounts_editor_add_pane.ui b/ui/accounts_editor_add_pane.ui
index cfe259021..8a38f67a9 100644
--- a/ui/accounts_editor_add_pane.ui
+++ b/ui/accounts_editor_add_pane.ui
@@ -7,34 +7,29 @@
     <property name="title" translatable="yes">Add an account</property>
     <property name="has_subtitle">False</property>
     <child>
-      <object class="GtkGrid">
+      <object class="GtkButton" id="back_button">
         <property name="visible">True</property>
+        <property name="receives_default">True</property>
+        <signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
         <child>
-          <object class="GtkButton" id="back_button">
+          <object class="GtkImage">
             <property name="visible">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="no_show_all">True</property>
-                <property name="icon_name">go-previous-symbolic</property>
-              </object>
-            </child>
+            <property name="no_show_all">True</property>
+            <property name="icon_name">go-previous-symbolic</property>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
         </child>
       </object>
+      <packing>
+        <property name="pack_type">start</property>
+        <property name="position">1</property>
+      </packing>
     </child>
     <child>
       <object class="GtkGrid">
         <property name="visible">True</property>
         <property name="column_spacing">12</property>
         <child>
-          <object class="GtkSpinner" id="create_spinner">
+          <object class="GtkSpinner" id="action_spinner">
             <property name="visible">True</property>
           </object>
           <packing>
@@ -43,16 +38,13 @@
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="create_button">
-            <property name="label" translatable="yes">_Create</property>
+          <object class="GtkButton" id="action_button">
+            <property name="label" translatable="yes">_Next</property>
             <property name="visible">True</property>
             <property name="sensitive">False</property>
             <property name="receives_default">True</property>
             <property name="use_underline">True</property>
-            <signal name="clicked" handler="on_create_button_clicked" swapped="no"/>
-            <style>
-              <class name="suggested-action"/>
-            </style>
+            <signal name="clicked" handler="on_action_button_clicked" swapped="no"/>
           </object>
           <packing>
             <property name="left_attach">1</property>
@@ -89,13 +81,14 @@
                 <property name="visible">True</property>
                 <property name="margin">24</property>
                 <child>
-                  <object class="GtkGrid" id="pane_content">
+                  <object class="GtkStack" id="stack">
                     <property name="visible">True</property>
                     <child>
                       <object class="GtkFrame">
                         <property name="visible">True</property>
                         <property name="label_xalign">0</property>
                         <property name="shadow_type">in</property>
+                        <property name="valign">start</property>
                         <child>
                           <object class="GtkListBox" id="details_list">
                             <property name="visible">True</property>
@@ -105,102 +98,98 @@
                         </child>
                       </object>
                       <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
+                        <property name="name">user_settings</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkGrid" id="receiving_panel">
+                      <object class="GtkBox">
                         <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkGrid" id="receiving_panel">
                             <property name="visible">True</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="label_xalign">0</property>
-                            <property name="shadow_type">in</property>
                             <child>
-                              <object class="GtkListBox" id="receiving_list">
+                              <object class="GtkLabel">
                                 <property name="visible">True</property>
-                                <property name="selection_mode">none</property>
-                                <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
+                                <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="label_xalign">0</property>
+                                <property name="shadow_type">in</property>
+                                <child>
+                                  <object class="GtkListBox" id="receiving_list">
+                                    <property name="visible">True</property>
+                                    <property name="selection_mode">none</property>
+                                    <signal name="keynav-failed" handler="on_list_keynav_failed" 
swapped="no"/>
+                                  </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>
-                      </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>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</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">
+                          <object class="GtkGrid" id="sending_panel">
                             <property name="visible">True</property>
-                            <property name="label_xalign">0</property>
-                            <property name="shadow_type">in</property>
                             <child>
-                              <object class="GtkListBox" id="sending_list">
+                              <object class="GtkLabel">
+                                <property name="visible">True</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="selection_mode">none</property>
-                                <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
+                                <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="selection_mode">none</property>
+                                    <signal name="keynav-failed" handler="on_list_keynav_failed" 
swapped="no"/>
+                                  </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>
                       </object>
                       <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">2</property>
+                        <property name="name">server_settings</property>
                       </packing>
                     </child>
-                    <style>
-                      <class name="geary-accounts-editor-pane-content"/>
-                    </style>
-
                   </object>
                 </child>
               </object>
diff --git a/ui/accounts_editor_list_pane.ui b/ui/accounts_editor_list_pane.ui
index 2f5e6af27..975853a4b 100644
--- a/ui/accounts_editor_list_pane.ui
+++ b/ui/accounts_editor_list_pane.ui
@@ -7,6 +7,19 @@
     <property name="title" translatable="yes">Accounts</property>
     <property name="has_subtitle">False</property>
     <property name="show_close_button">True</property>
+    <child>
+      <object class="GtkButton" id="add_button">
+        <property name="label" translatable="yes">_Add</property>
+        <property name="tooltip_text" translatable="yes">Add an account</property>
+        <property name="visible">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_underline">True</property>
+        <signal name="clicked" handler="on_add_button_clicked"/>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
   </object>
   <object class="GtkAdjustment" id="pane_adjustment">
     <property name="upper">100</property>
@@ -103,7 +116,6 @@
                           <object class="GtkListBox" id="accounts_list">
                             <property name="visible">True</property>
                             <property name="selection_mode">none</property>
-                            <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
                             <signal name="row-activated" handler="on_row_activated" swapped="no"/>
                           </object>
                         </child>
@@ -115,53 +127,9 @@
                         <property name="top_attach">1</property>
                       </packing>
                     </child>
-                    <child>
-                      <object class="GtkLabel" id="add_service_label">
-                        <property name="visible">True</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="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="valign">start</property>
-                            <property name="selection_mode">none</property>
-                            <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
-                            <signal name="row-activated" handler="on_row_activated" swapped="no"/>
-                          </object>
-                        </child>
-                        <child type="label_item">
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
                     <style>
                       <class name="geary-accounts-editor-pane-content"/>
                     </style>
-
                   </object>
                 </child>
               </object>


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